尝试在海量数据上更有效地使用索引.
我有一个开源应用程序,可以将数百万条记录记录到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;
条件通常包括:
> world_id / x / y / z坐标(查询默认为用户周围的半径,因此几乎总是使用坐标)
> epoch(所有查询默认为过去三天,用户需要在更长的时间范围内覆盖此内容)
> action_id和/或player_id(一半时间,用户正在寻找谁做了特定操作或特定玩家造成的操作.)
>剩余查询可以是任意组合,block_id值与玩家或动作相结合等.随机
GROUP BY – 默认情况下,应用程序按特定字段分组,以便用户不会看到同一个播放器/操作/块的100个重复事件,他们只能看到一个带有计数的记录.
action_id,player_id,block_id,DATE(FROM_UNIXTIME(epoch))
ORDER BY总是prism_data.epoch DESC,x ASC,z ASC,y ASC,id DESC.时间是这样的,用户首先看到最近的事件.其余的是“回滚”引擎以正确的顺序获取事物.
这是一个没有订单/组的示例查询:
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;
使用索引:INDEXlocation(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,一个结合了纪元.对于某些查询,这种方法很有效,但对于其他查询则不然.对于使用world_id,player_id和epoch的查询,它只选择world_id / x / y / z索引.
>我可以/我应该在多个索引中包含一列吗?也许是一个完整位置的索引,一个是world_id / player_id / epoch的索引?我无法确定mysql使用什么逻辑来选择哪个索引最适合,但我假设如果索引使用了更多mysql需要的列,它将选择那个.如果这有助于我的查询,那么写入的轻微性能是值得的.
>我应该创建一个索引,其中包含我按分组排序的所有字段吗?我的解释经常显示使用filesort,我知道这是性能的主要痛点.
>即使它们在组合索引中,在大多数字段上使用单个索引是否有任何好处?
很抱歉长时间阅读.
我正在对我们使用不同索引设置的5个最常见查询进行大量分析,但感觉我可能缺少一些基础知识.在继续之前,我宁愿让一些真正的专家在我缺少的东西上学习.
最佳答案 只是一个简单的说明,因为这种事情一次又一次地被看到:prism_worlds上的JOIN是不必要的,因为你(很可能)不需要该表中的数据.您基本上要求数据库“给我一个名称等于’某事’的世界名称”.请改用标量子查询.
在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作业到来并再次更新它们.