使用MySQL已有15年的经验,以前从未见过类似的东西。
具有400万条记录的MySQL表。
我发现性能上有一个非常奇怪的“酸点”(与“最佳点”相反),这取决于WHERE子句与ORDER BY子句相匹配的行数。
如果行数少,则性能很好。如果行数很大,则性能又很好。但是,如果WHERE匹配的行数在中间,大约8000行,那么性能将突然变差。
这是我首先注意到的查询速度缓慢:
SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve A%' ORDER BY generation DESC LIMIT 5;
有8978行与此WHERE子句匹配。
在我杀死它之前,它跑了50秒钟。然后考虑以下查询:
SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve Aa%' ORDER BY generation DESC LIMIT 5;
该WHERE子句匹配的行较少,只有1400个,因此我们可以预期性能会更好。它的确是。该查询将在0.06444050秒内完成。但是,那是巨大的差异。即使在O(N ^ 2)的ORDER BY子句中发生了某种排序,我们也不会期望有如此大的差异。不过,较小的机器更快也并不奇怪。...
但是,这是真正很奇怪的部分。考虑以下查询:
SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve%' ORDER BY generation DESC LIMIT 5;
该WHERE子句匹配154,119行。但是性能很好,可以在0.15081400秒内完成。
我想知道MySQL是否以一种奇怪的方式优化了这些查询。
是否先对行进行排序,然后遍历它们以查找WHERE匹配项?在那种情况下,较大的WHERE匹配集会更好,因为在订购后,它将很快找到其5个匹配命中。另一方面,如果WHERE匹配项很少,则首先对行进行排序会很慢。
如果它首先找到与WHERE匹配的行,然后再对它们进行排序,那么在小型WHERE匹配中这将很快,而在大型WHERE匹配中则很慢。
在大型WHERE比赛和小型WHERE比赛中速度很快,而在中型WHERE比赛中速度慢的事实是没有道理的,除非MYSQL正在分析查询,在极端情况下选择正确的方法,但选择在中间情况下,在两种情况之间的边界上使用了错误的方法。
顺便说一句,当前在“名称”和“世代”列上都有索引。我还尝试了两列INDEX(name,generation),但这没有帮助。我还尝试了INDEX(generation,names),以进行完整性检查。
这是所有三个的EXPLAIN输出。慢一个,然后两个快。您可以看到MySQL在前两个方面采用相同的方法,而在后一个方面采用不同的方法。
mysql> explain SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve A%' ORDER BY generation DESC LIMIT 5;
+----+-------------+---------------------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | lineageServer_lives | NULL | index | name | generation | 9 | NULL | 1343 | 0.37 | Using where |
+----+-------------+---------------------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve%' ORDER BY generation DESC LIMIT 5;
+----+-------------+---------------------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | lineageServer_lives | NULL | index | name | generation | 9 | NULL | 71 | 7.02 | Using where |
+----+-------------+---------------------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (1.65 sec)
mysql> explain SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve Aa%' ORDER BY generation DESC LIMIT 5;
+----+-------------+---------------------+------------+-------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+-------+---------------+------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | lineageServer_lives | NULL | range | name | name | 256 | NULL | 1400 | 100.00 | Using index condition; Using filesort |
+----+-------------+---------------------+------------+-------+---------------+------+---------+------+------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)
还要注意过滤器列中的区别。
因此,我相信对于最后一个,MySQL正在首先使用“名称”索引测试WHERE,仅看到1400个结果,然后说:“甜,不是很多,现在我们可以进行文件排序以获取顺序限制。”在前两个中,它看到WHERE子句的结果太多,无法对其进行文件排序,而是决定先遍历ORDER BY顺序的行,然后查找它可以找到的前5个WHERE匹配项。
实际上,如果是这样,那么如果我要求较少的比赛,它应该变得更快...嗯...不是更快,花了96秒:
96.20525475 | SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve A%' ORDER BY generation DESC LIMIT 1
这里的问题是数据不是随机分布的。对于以“ Eve A”开头的名称,它们的生成值较低。因此,要按代先排序,然后自上而下查找“ Eve A”,我们必须遍历所有4M记录。通过更改生成顺序,并过滤掉表中非常常见的默认值-1,我们可以获得更好的结果(0.00262775秒):
SELECT name FROM lineageServer_lives WHERE name LIKE 'Eve A%' and generation > 0 ORDER BY generation ASC LIMIT 1;
当然,这些命令的顺序错误,而不是我想要的结果。但这有助于解释发生的情况(以其他方式排序时,这些“夏娃A”匹配项位于列表的末尾,因此查找起来很慢)。
如果我将索引强加在“名称”列上,性能会提高:
SELECT name FROM lineageServer_lives force index( name ) WHERE name LIKE 'Eve%' ORDER BY generation DESC LIMIT 5
这将在0.2秒内运行。 MySQL根据名称列查找所有匹配的行,然后根据生成列对结果进行文件排序。但是,随着WHERE子句的行数越来越多,性能会下降,因此它不是通用解决方案。
现在,我了解这里发生了什么,我将结束这个问题。希望将来会有其他人从此分析中受益。