我想通过解释非常相似的查询以及对性能的巨大影响来帮助理解数据输出。我有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
我的执行时间超过10秒。
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
| 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
上的条件可能描述了一个非常狭窄的窗口。但MySQL不知道,因为你没有告诉MySQL,所以它不能考虑到这一点。它当然也没有能力根据列名的含义做出决定。
由于你有gauge
的索引,对于一个高值(例如g.gauge >= your_max_value_in_that_column
),MySQL实际上应该能够发现只有少数几行并且应该使用更好的执行计划。否则,MySQL基本上一无所知。尝试在很宽的范围内改变窗口大小(例如g.gauche >= 100000 AND g.droite <= 200000
); MySQL不会在rows
中显示明显不同的数字,除非你接近列的限制(并且有一个索引)。对于某些范围,第一个查询实际上应该变得更快,因为它更接近MySQL假设的数据分布。
那么如何告诉MySQL您的数据分布?
有可能将您的信息编码为spatial data(一个点)及其上的索引。然后你可以找到位于2d矩形中的点,MySQL现在可以理解这实际上是一个包含有限数据量的非常小的矩形。您的数据不一定是几何数据,只需要您可以在2维中对其进行编码。
假设我的假设是正确的,你也可以使用(g.gauche = 151579 or g.gauche = 151580)
,MySQL也应该能够理解这只是有限数量的数据。
你当然可以强制索引(或使用FROM geolocalisation g STRAIGHT_JOIN annonce a
)。你知道MySQL没有的东西,而且你通常不会告诉MySQL。缺点是这不能适用于其他情况,例如如果你(偶尔)在你的查询中使用更大的窗口,或者gauche <= droite
不再是真的。