WHERE和ORDER BY的性能不一致,具体取决于WHERE匹配项的大小

时间:2019-05-17 18:49:57

标签: mysql

使用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子句的行数越来越多,性能会下降,因此它不是通用解决方案。

现在,我了解这里发生了什么,我将结束这个问题。希望将来会有其他人从此分析中受益。

0 个答案:

没有答案