【问题标题】:Improving performance of mysql query with multiple where conditions on large tables在大型表上使用多个 where 条件提高 mysql 查询的性能
【发布时间】:2013-02-16 06:28:51
【问题描述】:

我有一个 mysql 表,它可能包含数百万行数据——在某些极端情况下高达 1 亿行。我开发的一个应用程序经常查询这些数据,我已经尽我所能来优化它——在大多数情况下,它工作得非常快,因为我们只搜索数据的一个非常小的子集(与位置相关) .

表结构:

CREATE TABLE `prism_actions` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `action_time` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `action_type` varchar(25) NOT NULL,
  `player` varchar(16) NOT NULL,
  `world` varchar(255) NOT NULL,
  `x` int(11) NOT NULL,
  `y` int(11) NOT NULL,
  `z` int(11) NOT NULL,
  `block_id` mediumint(5) unsigned NOT NULL,
  `block_subid` mediumint(5) unsigned NOT NULL,
  `data` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `x` (`x`),
  KEY `action_type` (`action_type`),
  KEY `player` (`player`),
  KEY `block_id` (`block_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

对于我们在 WHERE 语句中最常使用的字段,我有几个基本索引,当用于只有一个条件的查询时 - 它非常快。

我正在运行这些测试的示例表有 2200 万条记录。

例子:

SELECT prism_actions.id FROM prism_actions WHERE prism_actions.action_type = 'block-break' LIMIT 1000;
1000 rows in set (0.00 sec)

SELECT prism_actions.id FROM prism_actions WHERE prism_actions.block_id = 2 LIMIT 1000;
1000 rows in set (0.01 sec)

我的问题是,对于我们在查询中使用的每个条件(大多数查询通常有几个条件),查询需要更长的时间。

SELECT prism_actions.id FROM prism_actions WHERE prism_actions.action_type = 'block-break' AND prism_actions.block_id = 2 LIMIT 1000;
1000 rows in set (0.79 sec)

0.79 秒对于完整查询是可以接受的,但这只是使用了部分条件。

真正的查询更像是:

SELECT prism_actions.id FROM prism_actions WHERE prism_actions.action_type = 'block-break' AND prism_actions.player = 'viveleroi' AND prism_actions.block_id = 2 LIMIT 1000;
1000 rows in set (2.22 sec)

我们使用0.01 运行一个条件,使用0.79 运行两个条件,使用2.2 运行三个条件,这太长了。

我将研究如何更好地设计我的索引,但我对当前的数据库架构和索引非常满意。

但是,当这样一起使用时,我可以尝试什么来使条件更快?

更新

我花时间将表格转换为外键格式。 player、action_type 和 world 列数据被移动到单独的表中,并且它们的 ID 存储在原始表中。迁移数据花了几个小时。

但是,我正在重新运行我之前使用过的相同查询,虽然我看到一些查询速度有所提高,但我发现其他查询几乎没有变化。

上述 0.79 秒查询的转换版本运行速度大致相同:

SELECT prism_actions.id FROM prism_actions WHERE prism_actions.actiontype_id = 1 AND prism_actions.block_id = 2 LIMIT 1000;
1000 rows in set (0.73 sec)

block_id col 仍然有来自原始表模式的索引。

以 player_id 为条件的查询运行非常缓慢,因此我在列中添加了一个索引,现在查询速度非常快。

但是,在以真实用户的几个查询示例并针对此表结构更新它们之后,我发现速度没有变化。

SELECT prism_actions.id FROM prism_actions WHERE (prism_actions.actiontype_id = 2 OR prism_actions.actiontype_id = 1) AND (prism_actions.player_id = 1127) AND prism_actions.action_time >= '2013-02-22 07:47:54' LIMIT 1000;

以前拍5.83 sec,现在拍5.29 sec

编辑 - 似乎是时间戳。从上面的查询中取出时间戳条件会在 0.01 秒内返回结果。为时间戳添加索引没有任何作用 - 想法?

到目前为止,我真正看到的只是某些区域的速度略有提高,因为我们存储了重复的字符串而节省了少量文件空间 - 但到目前为止,还没有什么值得让数百名拥有如此大数据库的用户花费花一天时间转换数据。

对我可能索引内容等的其他方式有什么建议吗?

【问题讨论】:

  • 如果引擎是innodb是什么情况?他们慢吗?
  • 在文本列上搜索将不可避免地比在 int 列上搜索慢得多,这就是为什么在 where 子句中包含这些时间会大大增加。
  • @PradyutBhattacharya MyISAM 建议用于只读表。 InnoDB 会更慢。
  • 你可以使用分区吗?
  • 你的 MySQL 版本是多少?

标签: mysql performance query-optimization


【解决方案1】:

将所有文本列(动作类型、玩家、世界)所有这些都是文本列到一个新表中。

这将减少数据库大小并保留此表中的引用编号。

这将显着提高性能。

【讨论】:

  • 我想通了。我会看看我能做些什么来轻松地传输数据。我已经考虑过针对其他一些因素进行此调整。我曾经有一个预先编写的用于将表转换为外键的sn-p代码......需要去找到它。我会看看有什么影响并报告
  • 时间戳字段呢?大多数情况下,我们将查询限制在某个日期之后或之前 - 以某种方式将其存储为不同的格式(如 unix 纪元时间戳)会更有效吗?我将它设置为更新时的时间戳,以便我们可以将时间戳生成卸载到 mysql,这对我们的应用程序来说是一个巨大的性能提升。
  • 时间戳不会有任何影响。您可以使用整数字段而不是时间戳,但您需要使用 mysql 的 UNIX_TIMESTAMP() 函数将它们转换为并返回到 timstamp。但我认为在检索时从 int 转换为 timestamp 也需要时间。
  • 用一些初步发现更新了帖子。
【解决方案2】:

MySQL v5.5:您可以将PARTITION BY RANGE COLUMNS 创建为:

CREATE TABLE `prism_actions` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `action_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `action_type` varchar(25) NOT NULL,
  `player` varchar(16) NOT NULL,
  `world` varchar(255) NOT NULL,
  `x` int(11) NOT NULL,
  `y` int(11) NOT NULL,
  `z` int(11) NOT NULL,
  `block_id` mediumint(5) UNSIGNED NOT NULL,
  `block_subid` mediumint(5) UNSIGNED NOT NULL,
  `data` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `x` (`x`),
  KEY `action_type` (`action_type`),
  KEY `player` (`player`),
  KEY `block_id` (`block_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1
PARTITION BY RANGE COLUMNS(action_type, player, block_id)(
PARTITION p0 VALUES LESS THAN ('dddddddd','dddddddd',1000000),
PARTITION p1 VALUES LESS THAN ('gggggggg','gggggggg',2000000),
PARTITION p2 VALUES LESS THAN ('jjjjjjjj','jjjjjjjj',3000000),
PARTITION p3 VALUES LESS THAN ('mmmmmmmm','mmmmmmmm',4000000),
PARTITION p4 VALUES LESS THAN ('pppppppp','pppppppp',5000000),
PARTITION p5 VALUES LESS THAN ('ssssssss','ssssssss',6000000),
PARTITION p6 VALUES LESS THAN ('uuuuuuuu','uuuuuuuu',7000000),
PARTITION p7 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
);

最坏的情况是对于任何给定的(action_type, player, block_id) 集合,它们只能属于一个分区。因此,与原始查询相比,它更好。

加分速度,如果您可以分析列值的频率分布并相应地进行分区。以上分区为粗略间隔。

【讨论】:

  • 这是一个有趣的解决方案。我认为外键转换会更有益,但另外,这是一个分布式应用程序,我不想排除使用稍微旧版本mysql的人。
  • 如果这些文本字段是静态的(预定义集),是的,规范化(移动到另一个表)肯定会提高单个表的性能。您将加入以获得综合结果。因此选择然后加入;加入后不选择。
【解决方案3】:

我会单独留下您的表格,以防止在您的结果集之后需要再次加入。您只需要一个包含 where 的所有键列的单个索引,而不是每个单独的索引。我会尝试根据您首先遇到的最小结果集来优化它,例如 2200 万条记录,我敢打赌基于 Block_ID = 2 的记录不少,而基于玩家的记录则更少。

所以,我会有一个索引

create index multipart on prism_actions ( Player, Block_ID, Action_Type );

作为单个索引,而不是您当前拥有的单个字段。这允许引擎直接跳转到给定的玩家,现在从 2200 万,下降到 2000 个条目,到块 ID = 2 现在下降到 200,下降到 action_type = block break.... 20 条记录......显然只是记录计数的任意样本,但复合索引应该是您所需要的。

【讨论】:

  • 我对组合索引的主要关注是查询相对不可预测。位于该数据库之上的应用程序允许人们使用参数列表搜索他们的数据集,以匹配几乎所有字段。该查询可能正在寻找具有 action_types、坐标、block_ids 的玩家,或者它可能正在寻找玩家的所有记录,或者所有涉及块的动作。这对您的推荐有何影响?
  • 引擎可以使用多部分索引。您可能会选择根据可能更常见的搜索来构建几个索引。引擎将使用每个查询的最佳值。
  • 如果我要在我们已经拥有的基础上构建这样的组合索引,那将如何影响写入速度?对我来说,写入速度的优先级要低得多,因为当没有人真正在等待它们时,写入发生在队列中 - 但是当用户花费太长时间时,用户会注意到缓慢的读取。只是想权衡一下好处。
  • @BotskoNet,我不知道引擎在应用于仅使用此类复合索引的第二个或第三个字段的查询时如何充分利用复合索引......但是,如果你有几个基于不同的“可能”或“更常见”标准,您应该受益......您还可以让不同的索引在第一个位置有不同的字段,因此它可能会更好地进行查询匹配,或者仅仅因为它的第一个字段是一部分查询...我会尝试多重索引,看看你得到什么结果。
猜你喜欢
  • 1970-01-01
  • 2012-04-19
  • 2012-07-13
  • 2012-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-11
  • 2013-11-06
相关资源
最近更新 更多