MySQL 8.4 Reference Manual  /  Partitioning  /  Partition Pruning

26.4 分区修剪

优化器所知的分区修剪是基于一个相对简单的概念,这个概念可以被描述为“不要扫描那些没有匹配值的分区”。假设一个分区表 t1 是通过以下语句创建的:

CREATE TABLE t1 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY RANGE( region_code ) (
    PARTITION p0 VALUES LESS THAN (64),
    PARTITION p1 VALUES LESS THAN (128),
    PARTITION p2 VALUES LESS THAN (192),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);

如果你想要从一个 SELECT 语句中获得结果,比如这个:

SELECT fname, lname, region_code, dob
    FROM t1
    WHERE region_code > 125 AND region_code < 130;

很容易看出,应该返回的行都在分区 p0 或者 p3 中;也就是说,我们只需要搜索分区 p1p2 来找到匹配的行。通过限制搜索范围,可以在找到匹配行时节省大量时间和努力。这称为“修剪”(pruning)。当优化器可以利用分区修剪来执行这个查询时,执行该查询的速度比对一个没有分区的表快得多,这个表包含相同的列定义和数据。

优化器可以在以下两个情况下进行修剪:

  • partition_column = constant

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

在第一个情况下,优化器简单地评估分区表达式的值,确定哪个分区包含该值,并只扫描这个分区。在许多情况下,等号可以被其他算术比较替换,比如 <><=>=<>。一些使用 BETWEEN 子句的查询也可以利用分区修剪。请在后面的部分查看例子。

在第二个情况下,优化器对每个值列表中的值评估分区表达式,创建一个匹配分区的列表,然后只扫描这个分区列表中的分区。

SELECTDELETEUPDATE 语句支持分区修剪。一个 INSERT 语句也只访问一个分区来插入一行;即使是对一个使用 HASH 或者 KEY 分区的表来说,这一点也是真的,尽管这目前还没有在 EXPLAIN 的输出中显示。

修剪也可以应用于短范围,这个优化器可以将其转换为等价的值列表。例如,在前面的例子中,WHERE 子句可以被转换为 WHERE region_code IN (126, 127, 128, 129)。然后优化器可以确定第一个两个值在分区 p1 中,剩下的两个值在分区 p2 中,而其他分区中没有相关的值,因此不需要搜索以找到匹配行。

优化器也可以对使用 RANGE COLUMNS 或者 LIST COLUMNS 分区的表进行修剪,条件是这些表中的 WHERE 子句涉及多个列的相等或范围比较。

这种类型的优化可以在分区表达式由一个等式或者范围组成,并且可以被简化为一组等式,或者当分区表达式表示一种递增或递减关系时应用。修剪也可以用于对 DATE 或者 DATETIME 列分区的表,当分区表达式使用 YEAR() 或者 TO_ DAYS() 函数时。修剪也可以用于这样的表,当分区表达式使用 TO_ SECONDS() 函数时。

假设表 t2,基于一个DATE 列进行分区,然后使用以下语句创建该表:

CREATE TABLE t2 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY RANGE( YEAR(dob) ) (
    PARTITION d0 VALUES LESS THAN (1970),
    PARTITION d1 VALUES LESS THAN (1975),
    PARTITION d2 VALUES LESS THAN (1980),
    PARTITION d3 VALUES LESS THAN (1985),
    PARTITION d4 VALUES LESS THAN (1990),
    PARTITION d5 VALUES LESS THAN (2000),
    PARTITION d6 VALUES LESS THAN (2005),
    PARTITION d7 VALUES LESS THAN MAXVALUE
);

以下对 t2 的语句可以利用分区裁剪:

SELECT * FROM t2 WHERE dob = '1982-06-23';

UPDATE t2 SET region_code = 8 WHERE dob BETWEEN '1991-02-15' AND '1997-04-25';

DELETE FROM t2 WHERE dob >= '1984-06-21' AND dob <= '1999-06-21'

对于最后一个语句,优化器还可以这样做:

  1. 找到包含范围低端的分区

    YEAR('1984-06-21') 返回值 1984,该值在分区 d3 中找到。

  2. 找到包含范围高端的分区

    YEAR('1999-06-21') 评估为 1999,该值在分区 d5 中找到。

  3. 只扫描这两个分区以及可能位于它们之间的任何分区

    在这种情况下,这意味着只有分区 d3d4d5 被扫描。其余的分区可以安全地被忽略(并被忽略)。

Important

在对一个分区表执行语句时,引用了无效的 DATEDATETIME 值的 WHERE 子句中的值将被视为 NULL。这意味着查询如 SELECT * FROM partitioned_table WHERE date_column < '2008-12-00' 不会返回任何值(参见 Bug #40972)。

到目前为止,我们只看了使用 RANGE 分区的例子,但裁剪也可以应用于其他分区类型。

考虑一个由 LIST 分区的表,其中分区表达式是递增或递减的,如表 t3 所示。(在这个例子中,我们假设为了简洁起见,region_code 列的值仅限于1到10之间的值。)

CREATE TABLE t3 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY LIST(region_code) (
    PARTITION r0 VALUES IN (1, 3),
    PARTITION r1 VALUES IN (2, 5, 8),
    PARTITION r2 VALUES IN (4, 9),
    PARTITION r3 VALUES IN (6, 7, 10)
);

对于一个语句如 SELECT * FROM t3 WHERE region_code BETWEEN 1 AND 3,优化器确定值1、2和3在哪些分区中(r0r1)并跳过其余的分区(r2r3)。

对于使用 HASH[LINEAR] KEY 分区的表,分区裁剪也可以在 WHERE 子句中使用简单的 = 关系对一个用于分区表达式的列进行比较时。考虑到同样定义的表 t4,这样的语句可以被裁剪:

CREATE TABLE t4 (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    region_code TINYINT UNSIGNED NOT NULL,
    dob DATE NOT NULL
)
PARTITION BY KEY(region_code)
PARTITIONS 8;

可以进行裁剪的比较一个列值与常量的语句:

UPDATE t4 WHERE region_code = 7;

裁剪也可以用于短范围,因为优化器可以将这些条件转换为 IN 关系。例如,使用相同的表 t4 定义的前提下,这些查询可以被裁剪:

SELECT * FROM t4 WHERE region_code > 2 AND region_code < 6;

SELECT * FROM t4 WHERE region_code BETWEEN 3 AND 5;

在这两个情况下,WHERE 子句由优化器转换为 WHERE region_code IN (3, 4, 5)

Important

这个优化只有当范围大小小于分区数时才会使用。考虑以下语句:

DELETE FROM t4 WHERE region_code BETWEEN 4 AND 12;

WHERE 子句中的范围覆盖了9个值(4、5、6、7、8、9、10、11、12),但 t4 有8个分区。这意味着 DELETE 不能被裁剪。

当表使用 HASH[LINEAR] KEY 分区时,裁剪只能用于整数列。例如,这个语句无法使用裁剪,因为 dob 是一个DATE 列:

SELECT * FROM t4 WHERE dob >= '2001-04-14' AND dob <= '2005-10-15';

然而,如果表存储年份值在一个 INT 列中,那么查询 WHERE year_col >= 2001 AND year_col <= 2005 可以被裁剪。

使用自动分区存储引擎的表,如 MySQL 集群所使用的 NDB 存储引擎,可以在它们被显式分区时进行裁剪。