【华为云MySQL技术专栏】MySQL分区裁剪,提升分区表查询性能

举报
GaussDB 数据库 发表于 2025/05/09 09:04:03 2025/05/09
【摘要】 1. 背景介绍分区裁剪(Partition Pruning)是一种只适用于分区表的优化技术。当用户查询分区表时,往往只需要访问表中的特定分区。优化器通过分析 SQL 语句中的过滤条件,确定哪些分区是相关的,从而避免访问无关分区的优化过程,即为分区裁剪。分区裁剪是分区表提供的重要优化手段,通过分区裁剪,SQL 的执行效率可以得到大幅度提升。本文主要通过源码的方式来介绍分区裁剪的功能及原理。2....

1. 背景介绍

分区裁剪(Partition Pruning)是一种只适用于分区表的优化技术。当用户查询分区表时,往往只需要访问表中的特定分区。优化器通过分析 SQL 语句中的过滤条件,确定哪些分区是相关的,从而避免访问无关分区的优化过程,即为分区裁剪。

分区裁剪是分区表提供的重要优化手段,通过分区裁剪,SQL 的执行效率可以得到大幅度提升。本文主要通过源码的方式来介绍分区裁剪的功能及原理。

2. 约束条件

1)支持 SELECT、DELETE 以及 UPDATE 语句;

2)WHERE 条件限制如下:

partition_column OP constant (OP包含=<><=>=以及<>)

partition_column BETWEEN constant1 AND constant2

partition_column IN (constant1,constant2, ..., constantN)   

3)要求分区表达式是递增或者递减关系,比如YEAR()、TO_DAYS()、TO_SECONDS()等分区表达式。

3. 功能介绍

MySQL 的分区裁剪支持 MySQL 的所有一级分区和二级分区。

3.1 一级分区:创建 range 分区表

CREATE TABLE t_range (
   price  TINYINT UNSIGNED NOT NULL
)
PARTITION BY RANGE( price) (
    PARTITION p0 VALUES LESS THAN (10),
    PARTITION p1 VALUES LESS THAN (20),
    PARTITION p2 VALUES LESS THAN (30),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);

执行如下的 SELECT 语句,通过WHRER条件可以发现:该 SELECT 语句的匹配值不在 p0 分区和 p3 分区。

优化器通过分区裁剪之后,只需要扫描 p1 和 p2 分区,通过 EXPLAIN 查看执行计划中的 partitions 列,可以得到分区裁剪之后实际需要扫描的分区。 

mysql> EXPLAIN SELECT price FROM t_range WHERE price > 18 AND price < 23;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_range | p1,p2      | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

分区裁剪除了支持 SELECT 语句,还支持 DELETE、UPDATE 语句。如下的 DELETE 语句,通过分区裁剪之后需扫描的分区是 p2 和 p3 ,而 UPDATE 语句通过分区裁剪之后,只需扫描的分区是 p3。

mysql> EXPLAIN DELETE FROM t_range WHERE price > 20 AND price < 35;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | DELETE      | t1    | p2,p3      | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN UPDATE t_range SET price = 25  WHERE price > 30 AND price < 35;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | UPDATE      | t1    | p3         | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

3.2 二级分区:创建 range-hash 分区表

CREATE TABLE t_range_key (
   price TINYINT UNSIGNED NOT NULL
)
PARTITION BY RANGE(price) 
SUBPARTITION BY KEY (price) SUBPARTITIONS 2 (
    PARTITION p0 VALUES LESS THAN (10),
    PARTITION p1 VALUES LESS THAN (20),
    PARTITION p2 VALUES LESS THAN (30),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);

    二级分区采用的是 hash 分区,其分区个数是 

    执行如下的 SELECT 语句时,奇数会落在二级分区的 sp0 分区偶数落到二级分区的 sp1 分区。通过 WHERE 条件(WHERE price = 18 or price = 21),优化器进行分区裁剪之后,需扫描的分区是 p1_p1sp1 (一级分区p1下的二级分区 p1sp1 )和 p2_p2sp0 (一级分区 p2 下的二级分区 p2sp0 )

      mysql> EXPLAIN SELECT price FROM t_range_key WHERE price = 18 or price = 21;
      +----+-------------+-------------+-------------------+------+---------------+------+---------+------+------+----------+-------------+
      | id | select_type | table       | partitions        | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+-------------+-------------------+------+---------------+------+---------+------+------+----------+-------------+
      |  1 | SIMPLE      | t_range_key | p1_p1sp1,p2_p2sp0 | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
      +----+-------------+-------------+-------------------+------+---------------+------+---------+------+------+----------+-------------+
      1 row in set, 1 warning (0.00 sec)

      二级分区的分区裁剪,除了支持 SELECT 语句,还支持 DELETE 和 UPDATE 语句。

      DELETE 语句的 WHERE 条件(WHERE price = 5),经过优化器的分区裁剪后,需要扫描 p0_p0sp0 分区。UPDATE 语句的 WHERE 条件(WHERE price = 30),经过优化器的分区裁剪后,需要扫描 p3_p3sp1 分区。

      mysql> EXPLAIN DELETE FROM t_range_key WHERE price = 5;
      +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      | id | select_type | table       | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      |  1 | DELETE      | t_range_key | p0_p0sp0   | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
      +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      1 row in set, 1 warning (0.00 sec)
      
      mysql>
      mysql> EXPLAIN UPDATE t_range_key set price = 18 WHERE price = 30;
      +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      | id | select_type | table       | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      |  1 | UPDATE      | t_range_key | p3_p3sp1   | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
      +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      1 row in set, 1 warning (0.00 sec)

        4. 原理介绍

        分区裁剪本身是一个比较复杂的过程,优化器需要根据用户表的分区定义和 SQL 语句中指定的条件,抽取出相关的分区信息。由于 SQL 中的条件往往比较复杂,整个抽取逻辑的复杂性也随之增加。

        分区裁剪的主要函数是 prune_partitions。该函数通过不同的条件产生范围区间的 SEL_TREE 红黑树,通过遍历 SEL_TREE 红黑树的每个区间,来确定需要扫描的分区。

        4.1 分区裁剪函数prune_partitions调用关系

        首先会在 prepare 阶段进行分区裁剪,如果在 prepare 阶段分区裁剪没有完成,则会在 optimize 阶段再次尝试进行分区裁剪。下面是一个包含子查询的例子,在 optimize 阶段会再次对 t_range 表进行分区裁剪。

          For example, select * from t_range where price = 20;
          | Sql_cmd_dml::prepare
                -> Sql_cmd_select::prepare_inner
                    -> SELECT_LEX::prepare
                        -> SELECT_LEX::apply_local_transforms
                            -> prune_partitions
          
          For example, select * from t_range where price in (select price from t_range_1 where price = 20 );
          | SELECT_LEX_UNIT::optimize
              -> SELECT_LEX::optimize
                  -> JOIN::optimize
                      -> JOIN::prune_table_partitions
                          -> prune_partitions

          4.2 分区裁剪函数 prune_partitions 功能介绍

          1create_partition_index_description

          创建分区表的索引描述,并填充 PART_PRUNE_PARAM 结构。

          2bitmap_clear_all(&part_info->read_partitions)

          清除需要扫描的分区标记。

          3get_mm_tree

          构建单个或者多个 key part 的红黑树,其中 SEL_TREE::keys 是存储一个个单个 key part 的红黑树,SEL_TREE::merges 是存储多个 key part 的范围红黑树, SEL_TREE 节点类型是 SEL_ARG

            class SEL_ARG {
            public:
               ......
              SEL_ARG *left, *right;    /* R-B tree children */
              SEL_ARG *next, *prev;     /* Links for bi-directional interval list */ 
              SEL_ARG *parent{nullptr}; /* R-B tree parent (nullptr for root) */
              /*
                R-B tree of intervals covering keyparts consecutive to this
                SEL_ARG. See documentation of SEL_ARG GRAPH semantics for details.
              */
              SEL_ROOT *next_key_part{nullptr};
               ......
            };

            其中,参数介绍如下:

            1.png

            4find_used_partitions

            用于递归遍历 SEL_TREE 树中的每个 SEL_ARG 节点,收集每个分区的范围,并获取对应的分区 ID 号,并标记是否使用(设置partition_info::read_partitions)。

            4.3 举例分析

            表结构:

              CREATE TABLE t1 (
                `kp1` TINYINT UNSIGNED NOT NULL
              )
              PARTITION BY RANGE(`kp1`) (
                PARTITION p0 VALUES LESS THAN (10),
                PARTITION p1 VALUES LESS THAN (20),
                PARTITION p2 VALUES LESS THAN (30),
                PARTITION p3 VALUES LESS THAN MAXVALUE
              );
              CREATE TABLE t2 (
                `kp1` TINYINT UNSIGNED NOT NULL,
              `kp2` TINYINT UNSIGNED NOT NULL
              )
              PARTITION BY RANGE(`kp1` + `kp2`) (
                PARTITION p0 VALUES LESS THAN (10),
                PARTITION p1 VALUES LESS THAN (20),
                PARTITION p2 VALUES LESS THAN (30),
                PARTITION p3 VALUES LESS THAN MAXVALUE
              );

              1)SELECT * FROM t1 WHERE kp1 > 30 or kp1 <= 5;

              单 key part 的SEL_ARG,如图1所示,对于同一个 kp1 的 OR 关系,使用 next/prev 来关联:

              2.png

               1  key part 的 SEL_ARG

              遍历顺序:

              SEL_ARG(-∞,5]  -> 分区裁剪之后分区是 p0

              SEL_ARG(30,+) -> 分区裁剪之后分区是 p3

              分区裁剪之后,需要扫描的分区是分区 p0 和分区 p3。

              2select * from t2 where (kp1 = 1 AND (kp2=10 OR kp2=12)) OR (kp1=2 AND kp2=13);

               key part SEL_ARG,如图2所示,对于同一个 key part 的 OR 关系,使用 next/prev 来关联, 不同的 key part 使用 next_key_part 连接:

              3.png

               key part SEL_ARG

              遍历顺序:

              SEL_ARG[1, 1] SEL_ARG[10,10] -> 分区裁剪之后分区是 p1

              SEL_ARG[1, 1] SEL_ARG[12,12] -> 分区裁剪之后分区是 p1

              SEL_ARG[2, 2] SEL_ARG[13,13] -> 分区裁剪之后分区是 p1

              分区裁剪之后,需要扫描分区是分区 p1 

              5. 总结

              MySQL 分区表的分区裁剪,根据 SQL 语句的过滤条件产生范围区间的 SEL_TREE 红黑树,通过遍历 SEL_TREE 红黑树的每个区间来确定需要扫描的分区。MySQL 分区表的设计初衷是为了便于管理大表数据,将大表分解成更易管理的分区,客户根据业务需求,合理设计分区表和查询语句,通过分区裁剪机制可以显著提升大表查询性能。

              【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
              • 点赞
              • 收藏
              • 关注作者

              评论(0

              0/1000
              抱歉,系统识别当前为高风险访问,暂不支持该操作

              全部回复

              上滑加载中

              设置昵称

              在此一键设置昵称,即可参与社区互动!

              *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

              *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。