【问题标题】:Mysql: Optimizing Selecting rows from multiple ranges (using indexes?)Mysql:优化从多个范围中选择行(使用索引?)
【发布时间】:2009-11-12 12:17:46
【问题描述】:

我的表(项目):

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 (Query) 时我得到:

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

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

【问题讨论】:

    标签: mysql optimization select range


    【解决方案1】:

    来自 MySQL 参考手册中的7.2.5.1. The Range Access Method for Single-Part Indexes

    目前,对于空间索引的范围访问方式,MySQL 不支持合并多个范围。要解决此限制,您可以使用具有相同 SELECT 语句的 UNION,但您将每个空间谓词放在不同的 SELECT 中。

    所以你需要有两个不同选择的联合。

    【讨论】:

    • :( 不是我想要的答案,但至少我可以停止浪费时间试图弄清楚。
    【解决方案2】:

    你尝试过联合吗?以您的第二个示例为例,在下面添加“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
    

    【讨论】:

      【解决方案3】:

      您的查询确实合并了多个范围。

      它使用range 访问方法来组合p 上的多个范围(在连接中领先)。

      对于从p 返回的每一行,它检查从ps 检索所有行的最佳方法,以获取p.lftp.rgt给定 值。根据查询的选择性,它可能是对ps 的全扫描或对两个可能索引之一的索引查找。

      EXPLAIN 中显示的行数没有任何意义:EXPLAIN 仅显示最坏的结果。这并不一定意味着将检查所有这些行。他们是否会,优化器只能在运行时判断。

      关于无法合并多个范围的文档 sn-p 仅对 SPATIAL 索引有效(R-Tree 那些您通过 GEOMETRY 类型创建的索引)。这些索引适用于向上搜索(给定项目的祖先)但不向下搜索的查询。

      一个普通的B-Tree 索引可以组合多个范围。来自documentation

      对于所有类型的索引,多个范围条件结合ORAND构成一个范围条件。

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

      假设您有10,000 行,您的项目边界是0-5002000-2500。优化器将看到每个边界都将从索引中受益,range check 将导致两次范围访问,而单个全扫描会更好。

      如果您的项目边界是0-30005000-6000,情况可能会更糟。在这种情况下,优化器将进行 两次 全扫描,而一次就足够了。

      为了帮助优化器做出正确的决定,您应该按以下顺序在(lft, id)上制作覆盖索引:

      CREATE INDEX ix_lft_id ON projects (lft, id)
      

      在覆盖索引而不是范围条件上使用fullscan 的临界点是90%,这意味着您的实际计划中永远不会有超过一次的全扫描。

      【讨论】:

        猜你喜欢
        • 2020-01-11
        • 1970-01-01
        • 2019-02-08
        • 2016-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-17
        • 1970-01-01
        相关资源
        最近更新 更多