我正在玩(出于兴趣),在一个简单的邻接列表中检索一个节点树,并使用局部变量进行递归查询。
我到目前为止的解决方案很有趣但我不知道(这是我唯一的问题)为什么MySQL拒绝使用任何INDEX
来优化此查询。 MySQL不应该使用INDEX
来查找最近的孩子吗?
我很好奇为什么MySQL没有。即使我使用FORCE INDEX
,执行计划也不会改变。
这是到目前为止的查询,其中5
是父节点的ID:
SELECT
@last_id := id AS id,
parent_id,
name,
@depth := IF(parent_id = 5, 1, @depth + 1) AS depth
FROM
tree FORCE INDEX (index_parent_id, PRIMARY, index_both),
(SELECT @last_id := 5, @depth := -1) vars
WHERE id = 5 OR parent_id = @last_id OR parent_id = 5
请注意,原因不能是小数据集,因为当我指定FORCE INDEX (id)
或FORCE INDEX (parent_id)
或FORCE INDEX (id, parent_id)
时,行为不会改变...
文档说:
您还可以使用FORCE INDEX,其作用类似于USE INDEX(index_list),但另外还假设表扫描非常昂贵。换句话说,只有在无法使用某个给定索引查找表中的行时才使用表扫描。
必定会有一些东西会导致查询无法使用INDEX,但我不明白它是什么。
免责声明:我知道在SQL中存储和检索分层数据的方法有很多种。我知道嵌套集模型。我不是在寻找替代实现。我不是在寻找嵌套集。
我也知道查询本身就是坚果并产生错误的结果。
我只是想(详细地)了解为什么MySQL在这种情况下没有使用INDEX
。
答案 0 :(得分:2)
原因在于 WHERE 子句中使用 OR 条件。
为了说明,请尝试再次运行查询,这次仅使用id = 5
条件,并获取(EXPLAIN输出):
+----+-------------+------------+--------+--------------------+---------+---------+-------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+--------------------+---------+---------+-------+------+----------------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | tree | const | PRIMARY,index_both | PRIMARY | 4 | const | 1 | |
| 2 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+------------+--------+--------------------+---------+---------+-------+------+----------------+
同样,这次只有parent_id = @last_id OR parent_id = 5
条件,并获得:
+----+-------------+------------+--------+-----------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+-----------------+------+---------+------+------+----------------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | tree | ALL | index_parent_id | NULL | NULL | NULL | 10 | Using where |
| 2 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+------------+--------+-----------------+------+---------+------+------+----------------+
MySQL在同一查询中处理多个索引并不太好。 AND条件下情况略好一些;一个人更有可能看到index_merge优化而不是index union优化。
随着版本的推进,情况正在改善,但我已经测试了版本5.5
的查询,这是当前最新的生产版本,结果如您所述。
要解释为什么这很困难,请考虑:两个不同的索引将回答查询的两个不同条件。一个将回答id = 5
,另一个回答parent_id = @last_id OR parent_id = 5
(BTW后面的 OR 没有问题,因为这两个术语都是在同一个索引中处理的)。
没有一个索引可以回答这两个索引,因此忽略FORCE INDEX
指令。请参阅FORCE INDEX
表示MySQL必须在表扫描中使用 索引。这并不意味着它必须在表扫描中使用多个索引。
所以MySQL遵循这里的文档规则。但为什么这么复杂呢?因为要使用这两个索引来回答,MySQL必须从两者中收集结果,将一个存储在一些临时缓冲区中,同时管理第二个。然后必须通过该缓冲区来过滤掉相同的行(某些行可能适合所有条件)。然后扫描该缓冲区以返回结果。
但是请等一下,该缓冲区本身没有编入索引。过滤重复项不是一项明显的任务。所以MySQL更喜欢在原始表上工作并在那里进行扫描,并避免一切混乱。
当然这是可以解决的。 Oracle的工程师可能会改进这一点(最近他们一直在努力改进查询执行计划),但我不知道这是否在TODO任务上,或者它是否具有高优先级。