按范围分区的表是以这样的方式进行分区的:每个分区包含分区表达式值在给定范围内的行。范围应连续但不能重叠,并且使用 VALUES LESS THAN
运算符定义。在以下几个示例中,假设您正在创建一个类似于以下内容的表,以保存 20 家视频店的员工记录,编号为 1 到 20
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
);
此处使用的 employees
表没有主键或唯一键。虽然这些示例在当前讨论的目的中按所示工作,但您应该记住,在实践中,表极有可能具有主键、唯一键或两者兼而有之,并且分区列的允许选择取决于用于这些键的列(如果有)。有关这些问题的讨论,请参阅 第 26.6.1 节,“分区键、主键和唯一键”。
此表可以通过多种方式按范围分区,具体取决于您的需求。一种方法是使用 store_id
列。例如,您可能决定通过添加 PARTITION BY RANGE
子句以 4 种方式对表进行分区,如下所示
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN (21)
);
在此分区方案中,所有与在商店 1 到 5 工作的员工相对应的行都存储在分区 p0
中,与在商店 6 到 10 工作的员工相对应的行都存储在分区 p1
中,依此类推。每个分区都是按顺序定义的,从最低到最高。这是 PARTITION BY RANGE
语法的一个要求;您可将其视为在 C 或 Java 中类似于一系列 if ... elseif ...
语句。
很容易确定包含数据 (72, 'Mitchell', 'Wilson', '1998-06-25', DEFAULT, 7, 13)
的新行将插入分区 p2
中,但是当您的连锁店增加第 21 家商店时会发生什么?在此方案下,没有规则涵盖 store_id
大于 20 的行,因此会发生错误,因为服务器不知道将其放置在何处。您可以通过在 CREATE TABLE
语句中使用 “catchall” VALUES LESS THAN
子句来避免这种情况,该子句提供大于显式命名的最高值的 所有值
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
(与本章中的其他示例一样,我们假设默认存储引擎是 InnoDB
。)
另一种避免在找不到匹配值时发生错误的方法是在 INSERT
语句中使用 IGNORE
关键字。有关示例,请参阅 第 26.2.2 节,“列表分区”。
MAXVALUE
代表一个始终大于最大可能的整数值的整数值(用数学语言来说,它充当 最小上界)。现在,所有 store_id
列值大于或等于 16(定义的最高值)的行都存储在分区 p3
中。在将来的某个时刻——当商店数量增加到 25、30 或更多时——您可以使用 ALTER TABLE
语句为商店 21-25、26-30 等添加新分区(有关如何执行此操作的详细信息,请参阅 第 26.3 节,“分区管理”)。
以同样的方式,您可以根据员工职位代码对表进行分区,即根据 job_code
列值的范围进行分区。例如,假设两位数职位代码用于普通(店内)员工,三位数代码用于办公室和支持人员,四位数代码用于管理职位,则可以使用以下语句创建分区表
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (job_code) (
PARTITION p0 VALUES LESS THAN (100),
PARTITION p1 VALUES LESS THAN (1000),
PARTITION p2 VALUES LESS THAN (10000)
);
在这种情况下,所有与店内员工相关的行都将存储在分区 p0
中,与办公室和支持人员相关的行都存储在 p1
中,与经理相关的行都存储在分区 p2
中。
在 VALUES LESS THAN
子句中使用表达式也是可能的。但是,MySQL 必须能够将表达式的返回值评估为 LESS THAN
(<
) 比较的一部分。
与其根据商店编号划分表数据,不如使用基于两个 DATE
列之一的表达式。例如,假设您希望根据每个员工离开公司的年份进行分区,即 YEAR(separated)
的值。此处显示了一个实现此类分区方案的 CREATE TABLE
语句示例
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY RANGE ( YEAR(separated) ) (
PARTITION p0 VALUES LESS THAN (1991),
PARTITION p1 VALUES LESS THAN (1996),
PARTITION p2 VALUES LESS THAN (2001),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
在此方案中,对于所有在 1991 年之前离开的员工,其行都存储在分区 p0
中;对于那些在 1991 年到 1995 年之间离开的员工,其行都存储在 p1
中;对于那些在 1996 年到 2000 年之间离开的员工,其行都存储在 p2
中;对于任何在 2000 年之后离开的员工,其行都存储在 p3
中。
还可以根据 TIMESTAMP
列的值按 RANGE
对表进行分区,使用 UNIX_TIMESTAMP()
函数,如本示例所示
CREATE TABLE quarterly_report_status (
report_id INT NOT NULL,
report_status VARCHAR(20) NOT NULL,
report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
PARTITION p9 VALUES LESS THAN (MAXVALUE)
);
不允许使用任何其他涉及 TIMESTAMP
值的表达式。(请参阅错误 #42849。)
当满足以下一个或多个条件时,范围分区特别有用
您希望或需要删除 “旧” 数据。如果您使用的是之前为
employees
表显示的分区方案,则只需使用ALTER TABLE employees DROP PARTITION p0;
即可删除与在 1991 年之前停止为公司工作的员工相关的所有行。(有关更多信息,请参阅 第 15.1.9 节,“ALTER TABLE 语句” 和 第 26.3 节,“分区管理”)。对于具有大量行的表,这可能比运行诸如DELETE FROM employees WHERE YEAR(separated) <= 1990;
的DELETE
查询效率更高。您希望使用包含日期或时间值,或包含来自其他系列的值的列。
您经常运行直接依赖于用于对表进行分区的列的查询。例如,当执行诸如
EXPLAIN SELECT COUNT(*) FROM employees WHERE separated BETWEEN '2000-01-01' AND '2000-12-31' GROUP BY store_id;
的查询时,MySQL 可以快速确定只需要扫描分区p2
,因为剩余的分区不能包含任何满足WHERE
子句的记录。有关如何实现此功能的更多信息,请参阅 第 26.4 节,“分区修剪”。
此类型分区的变体是 RANGE COLUMNS
分区。通过 RANGE COLUMNS
进行分区,可以采用多个列来定义分区范围,这些范围既适用于将行放置到分区中,也适用于在执行分区修剪时确定包含或排除特定分区。有关更多信息,请参阅 第 26.2.3.1 节,“RANGE COLUMNS 分区”。
基于时间间隔的分区方案。 如果您希望在 MySQL 8.4 中实施基于时间范围或时间间隔的分区方案,您有两个选项
通过
RANGE
对表进行分区,并使用对DATE
、TIME
或DATETIME
列进行操作并返回整数的函数作为分区表达式,如下所示。CREATE TABLE members ( firstname VARCHAR(25) NOT NULL, lastname VARCHAR(25) NOT NULL, username VARCHAR(16) NOT NULL, email VARCHAR(35), joined DATE NOT NULL ) PARTITION BY RANGE( YEAR(joined) ) ( PARTITION p0 VALUES LESS THAN (1960), PARTITION p1 VALUES LESS THAN (1970), PARTITION p2 VALUES LESS THAN (1980), PARTITION p3 VALUES LESS THAN (1990), PARTITION p4 VALUES LESS THAN MAXVALUE );
在 MySQL 8.4 中,也可以通过
RANGE
对表进行分区,方法是根据TIMESTAMP
列的值进行分区,使用UNIX_TIMESTAMP()
函数,如以下示例所示。CREATE TABLE quarterly_report_status ( report_id INT NOT NULL, report_status VARCHAR(20) NOT NULL, report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) ( PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ), PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ), PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ), PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ), PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ), PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ), PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ), PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ), PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ), PARTITION p9 VALUES LESS THAN (MAXVALUE) );
在 MySQL 8.4 中,不允许使用涉及
TIMESTAMP
值的其他表达式。(参见 Bug #42849。)注意在 MySQL 8.4 中,也可以将
UNIX_TIMESTAMP(timestamp_column)
作为按LIST
分区的表的分区表达式。但是,通常这样做并不实用。通过
RANGE COLUMNS
对表进行分区,使用DATE
或DATETIME
列作为分区列。例如,members
表可以使用joined
列直接定义,如下所示。CREATE TABLE members ( firstname VARCHAR(25) NOT NULL, lastname VARCHAR(25) NOT NULL, username VARCHAR(16) NOT NULL, email VARCHAR(35), joined DATE NOT NULL ) PARTITION BY RANGE COLUMNS(joined) ( PARTITION p0 VALUES LESS THAN ('1960-01-01'), PARTITION p1 VALUES LESS THAN ('1970-01-01'), PARTITION p2 VALUES LESS THAN ('1980-01-01'), PARTITION p3 VALUES LESS THAN ('1990-01-01'), PARTITION p4 VALUES LESS THAN MAXVALUE );