Mysql 糟糕的执行计划
Mysql bad execution plan
我希望通过对非常相似的查询的解释以及对性能的巨大影响来理解数据输出方面的一些帮助。
我有 2 个表:annonce 和 geolocalisation。第一个包含租赁广告,第二个包含相应的位置。因此,我们在给定的地方搜索租金。
如果我使用默认计划
EXPLAIN
SELECT a.*, g.label AS geo_label, g.geo_url
FROM annonce a
INNER JOIN geolocalisation g ON a.geolocalisation_id = g.geolocalisation_id
WHERE a.categorie_id = 1 AND g.gauche >= 151579 AND g.droite <= 151580
AND couchage >= 2
ORDER BY FIELD(provenance_id, 2, 1), prix DESC, date_modification DESC, annonce_id ASC
我的执行时间超过10s
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
| 1 | SIMPLE | a | NULL | ref | geolocalisation_id,categorie_id | categorie_id | 4 | const | 502897 | 33.33 | Using index condition; Using where; Using filesort |
| 1 | SIMPLE | g | NULL | eq_ref | PRIMARY,droite,gauche | PRIMARY | 4 | vacamax.a.geolocalisation_id | 1 | 25.00 | Using where |
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
如果我将地理定位索引强制为 "gauche"
EXPLAIN
SELECT a.*, g.label AS geo_label, g.geo_url
FROM annonce a
INNER JOIN geolocalisation g ON a.geolocalisation_id = g.geolocalisation_id
WHERE a.categorie_id = 1 AND g.gauche >= 151579 AND g.droite <= 151580
AND couchage >= 2
ORDER BY FIELD(provenance_id, 2, 1), prix DESC, date_modification DESC, annonce_id ASC
我的执行时间为 .1s
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
| 1 | SIMPLE | g | NULL | range | gauche | gauche | 4 | NULL | 52785 | 33.33 | Using index condition; Using where; Using temporary; Using filesort |
| 1 | SIMPLE | a | NULL | ref | geolocalisation_id,categorie_id | geolocalisation_id | 5 | vacamax.g.geolocalisation_id | 13 | 16.66 | Using where |
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
结果是 188 行。在第一种情况下似乎测试了太多的行,但在第二种情况下过滤是有效的:地理定位是一个过滤器,应该在加入之前应用:1)你得到满足条件的地方 2)你找到具有这些条件的租金通过匹配表格放置 geolocalisation_id。
请赐教。
您 知道过滤地理定位之前比之后更聪明,因为您知道一些 MySQL 不知道的关于您的数据和查询的信息。
具体来说,MySQL 猜测它必须在第一个查询中查看 502897*1
行,在第二个查询中查看 52785*13=686205
行,并决定使用第一个。决定使用哪个执行计划还有其他因素,但它可以让您大致了解 MySQL 认为您的数据是什么样的。它与现实相去甚远(188 行),基于这种不正确的假设做出决定导致错误的策略也就不足为奇了。
事实上,即使我也只知道因为你告诉我,现在可以根据列名假设 gauche
总是小于 droite
,所以你的条件 g
大概描述了一个很窄的 window。但是MySQL不知道,因为你没有告诉MySQL,所以它不能考虑到这一点。而且它当然也没有能力根据列名的含义做出决定。
因为你在 gauge
上有索引,对于高值(例如 g.gauge >= your_max_value_in_that_column
),MySQL 实际上应该能够发现只有少数几行并且应该使用更好的执行计划。不然的话,MySQL基本是一头雾水。尝试在很宽的范围内改变 window 大小(例如 g.gauche >= 100000 AND g.droite <= 200000
); MySQL 不会在 rows
中显示明显不同的数字,除非您接近列的限制(并且在它们上有索引)。对于某些范围,第一个查询实际上应该变得更快,因为它更接近数据分布 MySQL 假设。
那么您如何了解 MySQL 您的数据分布?
可能可以将您的信息编码为 spatial data(一个点)和一个索引。然后你可以寻找位于二维矩形中的点,MySQL 现在可以理解这实际上是一个包含有限数据的非常小的矩形。不需要您的数据实际上是几何数据,只需要您可以将其编码为二维即可。
假设我的假设是正确的,你可能还可以使用(g.gauche = 151579 or g.gauche = 151580)
,MySQL应该也能理解,这只是有限的数据
你当然可以只强制索引(或使用FROM geolocalisation g STRAIGHT_JOIN annonce a
)。你知道 MySQL 不知道的东西,而且通常情况下,你无法告诉 MySQL。缺点是这不能适应其他情况,例如如果您(偶尔)在查询中使用更大的 windows,或者 gauche <= droite
不再正确。
我希望通过对非常相似的查询的解释以及对性能的巨大影响来理解数据输出方面的一些帮助。 我有 2 个表:annonce 和 geolocalisation。第一个包含租赁广告,第二个包含相应的位置。因此,我们在给定的地方搜索租金。 如果我使用默认计划
EXPLAIN
SELECT a.*, g.label AS geo_label, g.geo_url
FROM annonce a
INNER JOIN geolocalisation g ON a.geolocalisation_id = g.geolocalisation_id
WHERE a.categorie_id = 1 AND g.gauche >= 151579 AND g.droite <= 151580
AND couchage >= 2
ORDER BY FIELD(provenance_id, 2, 1), prix DESC, date_modification DESC, annonce_id ASC
我的执行时间超过10s
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
| 1 | SIMPLE | a | NULL | ref | geolocalisation_id,categorie_id | categorie_id | 4 | const | 502897 | 33.33 | Using index condition; Using where; Using filesort |
| 1 | SIMPLE | g | NULL | eq_ref | PRIMARY,droite,gauche | PRIMARY | 4 | vacamax.a.geolocalisation_id | 1 | 25.00 | Using where |
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
如果我将地理定位索引强制为 "gauche"
EXPLAIN
SELECT a.*, g.label AS geo_label, g.geo_url
FROM annonce a
INNER JOIN geolocalisation g ON a.geolocalisation_id = g.geolocalisation_id
WHERE a.categorie_id = 1 AND g.gauche >= 151579 AND g.droite <= 151580
AND couchage >= 2
ORDER BY FIELD(provenance_id, 2, 1), prix DESC, date_modification DESC, annonce_id ASC
我的执行时间为 .1s
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
| 1 | SIMPLE | g | NULL | range | gauche | gauche | 4 | NULL | 52785 | 33.33 | Using index condition; Using where; Using temporary; Using filesort |
| 1 | SIMPLE | a | NULL | ref | geolocalisation_id,categorie_id | geolocalisation_id | 5 | vacamax.g.geolocalisation_id | 13 | 16.66 | Using where |
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
结果是 188 行。在第一种情况下似乎测试了太多的行,但在第二种情况下过滤是有效的:地理定位是一个过滤器,应该在加入之前应用:1)你得到满足条件的地方 2)你找到具有这些条件的租金通过匹配表格放置 geolocalisation_id。 请赐教。
您 知道过滤地理定位之前比之后更聪明,因为您知道一些 MySQL 不知道的关于您的数据和查询的信息。
具体来说,MySQL 猜测它必须在第一个查询中查看 502897*1
行,在第二个查询中查看 52785*13=686205
行,并决定使用第一个。决定使用哪个执行计划还有其他因素,但它可以让您大致了解 MySQL 认为您的数据是什么样的。它与现实相去甚远(188 行),基于这种不正确的假设做出决定导致错误的策略也就不足为奇了。
事实上,即使我也只知道因为你告诉我,现在可以根据列名假设 gauche
总是小于 droite
,所以你的条件 g
大概描述了一个很窄的 window。但是MySQL不知道,因为你没有告诉MySQL,所以它不能考虑到这一点。而且它当然也没有能力根据列名的含义做出决定。
因为你在 gauge
上有索引,对于高值(例如 g.gauge >= your_max_value_in_that_column
),MySQL 实际上应该能够发现只有少数几行并且应该使用更好的执行计划。不然的话,MySQL基本是一头雾水。尝试在很宽的范围内改变 window 大小(例如 g.gauche >= 100000 AND g.droite <= 200000
); MySQL 不会在 rows
中显示明显不同的数字,除非您接近列的限制(并且在它们上有索引)。对于某些范围,第一个查询实际上应该变得更快,因为它更接近数据分布 MySQL 假设。
那么您如何了解 MySQL 您的数据分布?
可能可以将您的信息编码为 spatial data(一个点)和一个索引。然后你可以寻找位于二维矩形中的点,MySQL 现在可以理解这实际上是一个包含有限数据的非常小的矩形。不需要您的数据实际上是几何数据,只需要您可以将其编码为二维即可。
假设我的假设是正确的,你可能还可以使用(g.gauche = 151579 or g.gauche = 151580)
,MySQL应该也能理解,这只是有限的数据
你当然可以只强制索引(或使用FROM geolocalisation g STRAIGHT_JOIN annonce a
)。你知道 MySQL 不知道的东西,而且通常情况下,你无法告诉 MySQL。缺点是这不能适应其他情况,例如如果您(偶尔)在查询中使用更大的 windows,或者 gauche <= droite
不再正确。