繁体   English   中英

提高MySQL索引效率 - 多个索引中的列?

[英]Improving MySQL index efficiency - columns in multiple indexes?

尝试在海量数据上更有效地使用索引。

我有一个开源应用程序,可以将数百万条记录记录到MySQL数据库中。 我已经在Web开发中使用了多年的mysql数据库,并且我对选择有效的字段类型,索引为何如何/如何有用的基础知识等了解得足够多 - 但是我们的应用程序日志的数据量很大,而且很难确切地预测将要查询哪些列让我有点在水下。

应用程序记录玩家的事件。 我们有一个非常先进的净化系统,但有些服务器非常繁忙,仅仅八周就有5000万条记录。

在该大小,使用现有索引的事件,查询可能仍需要30-90秒。

主表模式(减去现有索引):

CREATE TABLE IF NOT EXISTS `prism_data` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `epoch` int(10) unsigned NOT NULL,
  `action_id` int(10) unsigned NOT NULL,
  `player_id` int(10) unsigned NOT NULL,
  `world_id` int(10) unsigned NOT NULL,
  `x` int(11) NOT NULL,
  `y` int(11) NOT NULL,
  `z` int(11) NOT NULL,
  `block_id` mediumint(5) DEFAULT NULL,
  `block_subid` mediumint(5) DEFAULT NULL,
  `old_block_id` mediumint(5) DEFAULT NULL,
  `old_block_subid` mediumint(5) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

WHERE条件最通常包括:

  • world_id/x/y/z坐标(查询默认为用户周围的半径,因此几乎总是使用坐标)
  • epoch (所有查询默认为过去三天​​,用户需要在更长的时间范围内覆盖此内容)
  • action_id和/或player_id (一半时间,用户正在寻找谁做了特定的操作或特定玩家造成的操作。)
  • 剩余查询可以是任意组合,block_id值与玩家或动作相结合等。随机

GROUP BY - 默认情况下,应用程序按特定字段分组,以便用户不会看到同一个播放器/操作/块的100个重复事件,他们只能看到一个带有计数的记录。

action_idplayer_idblock_idDATE(FROM_UNIXTIME(epoch))

ORDER BY总是prism_data.epoch DESC, x ASC, z ASC, y ASC, id DESC epoch是为了让用户首先看到最近的事件。 其余的是“回滚”引擎以正确的顺序获取事物。

这是一个没有订单/组的示例查询

SELECT *
FROM prism_data 
INNER JOIN prism_players p ON p.player_id = prism_data.player_id
INNER JOIN prism_actions a ON a.action_id = prism_data.action_id
INNER JOIN prism_worlds w ON w.world_id = prism_data.world_id 
LEFT JOIN prism_data_extra ex ON ex.data_id = prism_data.id 
WHERE w.world = 'DeuxTiersMondes'
AND (prism_data.x BETWEEN 668 AND 868)
AND (prism_data.y BETWEEN -33 AND 167)
AND (prism_data.z BETWEEN 358 AND 558);
LIMIT 1000;

使用索引: INDEX位置( world_id , x , z , y ); 找到1000行(或50秒找到所有64735)仍然需要15秒。

该查询的解释:

+----+-------------+------------+--------+---------------+----------+---------+--------------------------------+------+--------------------------+
| id | select_type | table      | type   | possible_keys | key      | key_len | ref                            | rows | Extra                    |
+----+-------------+------------+--------+---------------+----------+---------+--------------------------------+------+--------------------------+
|  1 | SIMPLE      | w          | ref    | PRIMARY,world | world    | 767     | const                          |    1 | Using where; Using index |
|  1 | SIMPLE      | prism_data | ref    | location      | location | 4       | minecraft.w.world_id           | 6155 | Using index condition    |
|  1 | SIMPLE      | a          | eq_ref | PRIMARY       | PRIMARY  | 4       | minecraft.prism_data.action_id |    1 | NULL                     |
|  1 | SIMPLE      | p          | eq_ref | PRIMARY       | PRIMARY  | 4       | minecraft.prism_data.player_id |    1 | NULL                     |
|  1 | SIMPLE      | ex         | ref    | data_id       | data_id  | 4       | minecraft.prism_data.id        |    1 | NULL                     |
+----+-------------+------------+--------+---------------+----------+---------+--------------------------------+------+--------------------------+

在我看来,寻找这个特定的价值应该快得多。 我们甚至没有对此查询进行排序/分组。

我的问题:

我认为为上面列出的每个常见条件设计索引是最有意义的。 即一个结合了world_id/x/y/z索引,一个结合了action_id/player_id ,一个结合了epoch 对于某些查询,这种方法很有效,但对于其他查询则不然。 对于使用world_id, player_id, and epoch的查询,它只选择world_id/x/y/z索引。

  • 我可以/应该在多个索引中包含一列吗? 也许是一个完整位置的索引,一个是world_id/player_id/epochworld_id/player_id/epoch 我无法确定mysql使用什么逻辑来选择哪个索引最适合,但我假设如果索引使用了更多mysql需要的列,它将选择那个。 如果这有助于我的查询,那么写入的轻微性能是值得的。
  • 我应该创建一个索引,其中包含我按顺序分组的所有字段吗? 我的解释经常显示Using filesort ,我知道这是性能的主要痛点。
  • 在大多数字段上使用单个索引是否有任何好处,即使它们在组合索引中?

很抱歉长时间阅读。

我正在对我们使用不同索引设置的5个最常见查询进行大量分析,但感觉我可能缺少一些基础知识。 在继续之前,我宁愿让一些真正的专家在我缺少的东西上学习。

只是一个简单的说明,因为这种事情一次又一次地被看到: prism_worlds上的prism_worlds是不必要的,因为你(很可能)不需要该表中的数据。 您基本上要求数据库“给我一个名称等于'某事'的世界名称”。 请改用标量子查询。

prism_worlds.world上创建一个唯一索引并运行查询

SELECT *
FROM prism_data 
WHERE prism_data.world_id = (SELECT w.world_id FROM prism_worlds AS w WHERE w.world = 'DeuxTiersMondes')
LIMIT 1000;

优化器将发现prism_data.world_id被约束为单个常量值。 MySQL将提前运行查询以找出此值并在查询中使用它。 请参阅EXPLAIN以了解执行的const -subquery。


关于prism_data.x.y.z :您可能想要为其创建几何列和空间索引。 如果您需要坚持单独的值,您可能希望将整个世界几何体分成固定大小的体素(由单个int表示),并使用简单几何体来确定哪个位置落入哪个体素。


我个人的解决方案是不要在这张桌子上添加太多的查询。 索引将使它变得缓慢而大。 使用cron作业填充报表(物化视图)以提前生成结果并使用它们,只要cron作业到来并再次更新它们。

MySQL(和其他RDMS系统)充分利用覆盖索引。 所以,如果您正在查找,请使用您的示例,

SELECT prism_data.id,
       prism_data.action_id,
       prism_data.world_id
  FROM prism_data 
 INNER JOIN prism_worlds w ON w.world_id = prism_data.world_id 
 WHERE w.world = 'DeuxTiersMondes'
   AND (prism_data.x BETWEEN 668 AND 868)
   AND (prism_data.y BETWEEN -33 AND 167)
   AND (prism_data.z BETWEEN 358 AND 558)
 ORDER BY prism_data.id DESC
 LIMIT 1000;

关于prism_data的以下BTREE索引可能会帮助一堆查询性能(几乎所有索引都是BTREE索引):

 (world_id, x, y, z, id, action_id, world_id)

prism_data上的所有查询都可以从索引中得到满足。 它被称为覆盖索引,因为服务器可以找到它需要满足的所有内容 - 覆盖 - 索引中的查询,因此不必反弹到数据表本身。 它将在world_id上执行索引标识扫描,然后在x上执行范围扫描,然后查看y和z值以匹配查询的其余部分。 然后它将提取id值,对它们进行排序,并返回LIMIT 1000部分结果集。

你绝对应该停止使用SELECT * 当您说SELECT *您拒绝MySQL了解您实际需要哪些数据列,因此您将失败优化器的逻辑,该逻辑选择覆盖原始表查询的索引查询。

如果您的数据在x和y上相当均匀地分布,并且您可以使用MyISAM,则可能需要考虑使用地理空间索引。 与普通索引相比,它们可以更好地随机访问x / y范围。

精心设计的索引设置会降低插入和更新的速度; 这绝对是一个权衡。

如果提供索引的前n列,MySQL可以使用复合索引。 因此,如果您在列a,b,c,d上有复合索引,那么如果您提供列a,b,MySQL可以使用该索引。 OTOH,如果你只在查询中提供了B,C,D列,MySQL就无法使用索引。 根据您在查询中可能使用的列的组合,在多个索引中包含列可能有意义也可能没有意义。 不要忘记为每个列/索引插入/更新/删除行需要额外的成本。

我不认为MySQL已经订购了索引,所以我怀疑索引不会对订购性能有所帮助,但我不确定。

根据您使用数据的方式,调查表格可能是有意义的,也许是按时代划分。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM