原因在于在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 INDEX
MySQL 必须在表扫描上使用索引。这并不意味着它必须在一次表扫描中使用多个索引。
所以 MySQL 遵循这里的文档规则。但为什么这么复杂?因为要使用两个索引来回答,MySQL 必须从两者收集结果,将一个存储在某个临时缓冲区中,同时管理第二个。然后必须遍历该缓冲区以过滤掉相同的行(某些行可能适合所有条件)。然后扫描该缓冲区以返回结果。
但是等等,那个缓冲区本身没有被索引。过滤重复项并不是一项显而易见的任务。所以 MySQL 更喜欢在原始表上工作并在那里进行扫描,并避免所有这些混乱。
当然这是可以解决的。Oracle 的工程师可能还在改进这一点(最近他们一直在努力改进查询执行计划),但我不知道这是否在 TODO 任务上,或者它是否具有高优先级。