MySQL 8.4 参考手册  /  分区  /  分区修剪

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;

很容易看出,应该返回的所有行都不在分区 p0p3 中;也就是说,我们只需要搜索分区 p1p2 即可找到匹配的行。通过限制搜索范围,与扫描表中的所有分区相比,可以花费更少的时间和精力来查找匹配的行。这种 剪掉 不需要的分区的操作称为 修剪。当优化器可以在执行此查询时利用分区修剪时,查询的执行速度可以比针对包含相同列定义和数据的非分区表执行相同查询快一个数量级。

每当 WHERE 条件可以简化为以下两种情况之一时,优化器就可以执行修剪

  • partition_column = constant

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

在第一种情况下,优化器只是针对给定值计算分区表达式,确定包含该值的分区,并仅扫描该分区。在许多情况下,等号可以用另一个算术比较运算符替换,包括 <><=>=<>。在 WHERE 子句中使用 BETWEEN 的某些查询也可以利用分区修剪。请参阅本节后面的示例。

在第二种情况下,优化器计算列表中每个值的分区表达式,创建一个匹配分区列表,然后仅扫描此分区列表中的分区。

SELECTDELETEUPDATE 语句支持分区修剪。INSERT 语句也仅访问每个插入行的一个分区;即使对于按 HASHKEY 分区的表也是如此,尽管这在 EXPLAIN 的输出中当前未显示。

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

对于使用 RANGE COLUMNSLIST COLUMNS 分区的表,优化器还可以对涉及多列上前述类型比较的 WHERE 条件执行修剪。

每当分区表达式由可以简化为一组相等关系的相等关系或范围组成,或者当分区表达式表示递增或递减关系时,就可以应用这种类型的优化。当分区表达式使用 YEAR()TO_DAYS() 函数时,修剪也可以应用于在 DATEDATETIME 列上分区的表。当分区表达式使用 TO_SECONDS() 函数时,也可以对这类表应用修剪。

假设表 t2DATE 列上分区,使用此处所示的语句创建

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。其余分区可以安全地忽略(并且会被忽略)。

重要提示

在针对分区表的语句的 WHERE 条件中引用的无效 DATEDATETIME 值将被视为 NULL。这意味着诸如 SELECT * FROM partitioned_table WHERE date_column < '2008-12-00' 之类的查询不会返回任何值(请参阅错误 #40972)。

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

考虑一个按 LIST 分区的表,其中分区表达式是递增或递减的,例如此处显示的表 t3。(在本例中,为了简洁起见,我们假设 region_code 列的值限制在 1 到 10 之间,包括 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 子句对分区表达式中使用的列使用简单的 = 关系的情况下,也可以进行分区修剪。考虑如下创建的表

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)

重要提示

仅当范围大小小于分区数时,才使用此优化。考虑以下语句

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 分区时,修剪只能用于整数列。例如,以下语句不能使用修剪,因为 dobDATE

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

但是,如果表在 INT 列中存储年份值,则可以修剪具有 WHERE year_col >= 2001 AND year_col <= 2005 的查询。

如果使用提供自动分区功能的存储引擎(例如 MySQL 集群使用的 NDB 存储引擎)的表被显式分区,则可以对其进行分区剪枝。