我的表(项目):
id, lft, rgt
1, 1, 6
2, 2, 3
3, 4, 5
4, 7, 10
5, 8, 9
6, 11, 12
7, 13, 14
您可能已经注意到,这是使用nested set model的分层数据。树很漂亮:
1
2
3
4
5
6
7
我想选择项目1和4下的所有子项目。我可以这样做:
SELECT p.id
FROM projects AS p, projects AS ps
WHERE (ps.id = 1 OR ps.id = 4)
AND p.lft BETWEEN ps.lft AND ps.rgt
然而,对于一个大表,这是非常慢的,当运行EXPLAIN(查询)我得到:
+----+-------------+-------+-------+------------------------+---------+---------+------+------+-------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------------+---------+---------+------+------+-------------------------------------------------+
| 1 | SIMPLE | ps | range | PRIMARY,lft,rgt,lftRgt | PRIMARY | 4 | NULL | 2 | Using where |
| 1 | SIMPLE | p | ALL | lft,lftRgt | NULL | NULL | NULL | 7040 | Range checked for each record (index map: 0x12) |
+----+-------------+-------+-------+------------------------+---------+---------+------+------+-------------------------------------------------+
(项目表上有lft,rgt和lft-rgt的索引。正如你所看到的,mysql不使用任何索引,并循环遍历7040记录)
我发现如果我只选择其中一个超级项目,mysql会设法使用这些索引:
SELECT p.id
FROM projects AS p, projects AS ps
WHERE ps.id = 1
AND p.lft BETWEEN ps.lft AND ps.rgt
解释为:
+----+-------------+-------+-------+------------------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------------+---------+---------+-------+------+-------------+
| 1 | SIMPLE | ps | const | PRIMARY,lft,rgt,lftRgt | PRIMARY | 4 | const | 1 | |
| 1 | SIMPLE | p | range | lft,lftRgt | lft | 4 | NULL | 7 | Using where |
+----+-------------+-------+-------+------------------------+---------+---------+-------+------+-------------+
最终,我的问题:我有什么方法可以选择匹配多个范围的行,并且仍然可以从索引中受益?
答案 0 :(得分:1)
编辑:
SELECT p.id
FROM projects AS p, projects AS ps
WHERE ps.id = 1
AND p.lft BETWEEN ps.lft AND ps.rgt
UNION
SELECT p.id
FROM projects AS p, projects AS ps
WHERE ps.id = 4
AND p.lft BETWEEN ps.lft AND ps.rgt
答案 1 :(得分:1)
来自MySQL参考手册中的7.2.5.1. The Range Access Method for Single-Part Indexes:
目前,MySQL不支持合并空间索引的范围访问方法的多个范围。要解决此限制,可以使用具有相同SELECT语句的UNION,除了将每个空间谓词放在不同的SELECT中。
所以你需要有两个不同选择的联合。
答案 2 :(得分:1)
您的查询确实合并了多个范围。
它使用range
访问方法来组合p
上的多个范围(在连接中领先)。
对于从p
返回的每一行,它会检查从ps
检索给定值p.lft
和{{1}的所有行的最佳方法}}。根据查询选择性,它可能是p.rgt
上的全扫描,也可能是两个可能索引之一的索引查找。
ps
中显示的行数没有任何意义:EXPLAIN
只显示最糟糕的结果。它并不一定意味着将检查所有这些行。他们是否会在优化程序中只能在运行时告知。
关于无法合并多个范围的文档片段仅对EXPLAIN
索引(SPATIAL
您在R-Tree
类型上创建的索引)有效。这些索引适用于向上搜索(给定项目的祖先)但不向下搜索的查询。
普通GEOMETRY
索引可以组合多个范围。来自documentation:
对于所有类型的索引,多个范围条件与
B-Tree
或OR
组合形成范围条件。
真正的问题是AND
中的优化器无法做出正确的决定:使用单个fullscan(带MySQL
前导),或进行多次范围扫描。
说,您有ps
行,项目边界为10,000
和0-500
。优化器将看到每个边界都将从索引中受益,2000-2500
将导致两个范围访问,而单个fullscan将更好。
如果你的项目边界是range check
和0-3000
,可能会更糟。在这种情况下,优化器将使两个全扫描,而一个就足够了。
为了帮助优化程序做出正确的决定,您应该按照以下顺序在5000-6000
上创建覆盖索引:
(lft, id)
在覆盖索引而不是范围条件上使用CREATE INDEX ix_lft_id ON projects (lft, id)
的转折点为fullscan
,这意味着您的实际计划中永远不会有超过一个完整扫描。