6

我正在玩弄(感兴趣),用简单邻接列表中的节点树检索使用局部变量的递归查询。使用不使用INDEX的查询变量进行SELECT选择

我迄今为止的解决方案很有趣,但我想知道为什么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 

Try live example at SQLfiddle

注意,之所以不能是小数据集,因为当我指定FORCE INDEX (id)FORCE INDEX (parent_id)FORCE INDEX (id, parent_id)时,行为不会改变...

该文档说:

您也可以使用FORCE INDEX,其行为像USE INDEX(index_list),但除了假定表扫描非常昂贵。换句话说,只有在无法使用某个给定索引来查找表中的行时才使用表扫描。

必须有一些呈现查询无法使用INDEX,但我不明白它是什么。


免责声明:我知道有不同的方式来存储和检索SQL分层数据。我知道嵌套集模型。我没有寻找替代实施。我不是在寻找嵌套集合。

我也知道查询本身是坚果,并产生错误的结果。

我只是想,为什么MySQL是不是在这种情况下使用INDEX理解(详细)。

+0

有时一个表有这么几条记录,使用索引的开销比读取整个表的时间要多。 – Randy 2012-07-09 21:56:21

+0

@randy现在有一个似是而非的论点... – xandercoded 2012-07-09 21:57:04

+0

@Randy看到更新的问题 – Kaii 2012-07-09 22:03:14

回答

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 union优化相比,更有可能看到index_merge优化。

随着版本的进步,情况正在改善,但是我已经测试过您在版本5.5上的查询,该版本位于当前最新的生产版本,结果如​​您所描述的那样。

要解释为什么这很困难,请考虑:两个不同的索引将针对查询的两个不同条件作出回答。一个将回答id = 5,另一个为parent_id = @last_id OR parent_id = 5(顺便说一句,内没有问题,因为两个条款都是从同一索引内处理的)。

没有一个索引可以为两者都回答,因此FORCE INDEX指令被忽略。看,FORCE INDEX说MySQL必须在表扫描上使用索引。这并不意味着它必须在表扫描中使用多个索引。

所以MySQL遵循这里的文档规则。但为什么这么复杂呢?因为要使用这两个索引来回答问题,MySQL必须从两者收集结果,在管理第二个时将其存放在一些临时缓冲区中。然后必须通过该缓冲区来过滤出相同的行(可能某行适合所有条件)。然后扫描该缓冲区以返回结果。

但是等等,那个缓冲本身本身没有索引。过滤重复项不是一项明显的任务。所以MySQL更喜欢在原始表上工作,并在那里进行扫描,并避免所有这些混乱。

当然这是可以解决的。甲骨文公司的工程师可能会改进这一点(最近他们一直在努力改进查询执行计划),但我不知道这是否在TODO任务上,或者它是否具有高优先级。

+0

非常感谢你为这个精心制作的答案! – Kaii 2012-07-10 16:27:21