7

我正在玩(出于兴趣)在一个简单的邻接列表中检索节点树,并使用局部变量进行递归查询。

到目前为止我的解决方案很有趣,但我想知道(这是我唯一的问题)为什么 MySQL 拒绝使用 anyINDEX来优化这个查询。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

在 SQLfiddle 尝试实时示例

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

文档说:

您还可以使用 FORCE INDEX,它的作用类似于 USE INDEX (index_list),但另外假设表扫描非常昂贵。换句话说,仅当无法使用给定索引之一来查找表中的行时,才使用表扫描。

一定有一些东西使查询无法使用索引,但我不明白它是什么。


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

我也知道查询本身很疯狂并且会产生错误的结果。

我只想(详细)了解为什么 MySQLINDEX在这种情况下不使用 an 。

4

1 回答 1

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优化而不是索引联合优化。

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

要解释为什么这很困难,请考虑:两个不同的索引将回答两个不同的查询条件。一个会回答id = 5,另一个回答(顺便说一句,后者内部的ORparent_id = @last_id OR parent_id = 5没有问题,因为这两个术语都是在同一个索引中处理的)。

没有一个索引可以同时回答这两个问题,因此该FORCE INDEX指令被忽略。看,FORCE INDEXMySQL 必须在表扫描上使用索引这并不意味着它必须在一次表扫描中使用多个索引。

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

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

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

于 2012-07-10T07:50:22.753 回答