Mysql:优化从多个范围中选择行(使用索引?)

时间:2009-11-12 12:17:47

标签: mysql optimization select range

我的表(项目):

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 | 
+----+-------------+-------+-------+------------------------+---------+---------+-------+------+-------------+

最终,我的问题:我有什么方法可以选择匹配多个范围的行,并且仍然可以从索引中受益?

3 个答案:

答案 0 :(得分:1)

你尝试过工会吗?拿你的第二个例子,在下面添加“union”和重复但匹配id 4.我不知道它是否会起作用,但这似乎是一个明显的尝试。

编辑:

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-TreeOR组合形成范围条件。

真正的问题是AND中的优化器无法做出正确的决定:使用单个fullscan(带MySQL前导),或进行多次范围扫描。

说,您有ps行,项目边界为10,0000-500。优化器将看到每个边界都将从索引中受益,2000-2500将导致两个范围访问,而单个fullscan将更好。

如果你的项目边界是range check0-3000,可能会更糟。在这种情况下,优化器将使两个全扫描,而一个就足够了。

为了帮助优化程序做出正确的决定,您应该按照以下顺序在5000-6000上创建覆盖索引:

(lft, id)

在覆盖索引而不是范围条件上使用CREATE INDEX ix_lft_id ON projects (lft, id) 的转折点为fullscan,这意味着您的实际计划中永远不会有超过一个完整扫描。