26.2.1 范围分区
一个使用范围分区的表是按照这样方式分区的:每个分区包含那些在给定范围内的行。范围应该是连续的但不重叠,并且可以使用 VALUES LESS THAN
运算符来定义。对于接下来的几个例子,假设你正在创建一个用于存储一家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
列。例如,你可能决定将表分为4个部分,通过添加一个 PARTITION BY RANGE
子句,如下所示:
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
。)
另一种避免在没有匹配值时出现错误的方法是使用 IGNORE
关键字作为 INSERT
语句的一部分。对于一个例子,请参阅 第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
列的值对表进行分区,使用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
值的表达式都不被允许。(见Bug #42849。)
基于时间范围分区特别有用,当满足以下条件之一时:
-
你想要或需要删除“old”数据。如果你正在使用之前用于
employees
表的分区方案,你可以简单地使用ALTER TABLE employees DROP PARTITION p0;
来删除所有与在1991年之前离职的员工相关的行。(见Section 15.1.9, “ALTER TABLE Statement”
和Section 26.3, “Partition Management”
,以获取更多信息。对于行数很大的表,这可以比运行一个类似于DELETE
查询的DELETE FROM employees WHERE YEAR(separated) <= 1990;
更高效。 -
你想要使用包含日期或时间值的列,或者包含由其他系列产生的值。
-
你经常运行依赖于用于分区表的列的查询。例如,当执行一个类似于
EXPLAIN SELECT COUNT(*) FROM employees WHERE separated BETWEEN '2000-01-01' AND '2000-12-31' GROUP BY store_id;
的查询时,MySQL 可以快速确定只有分区p2
需要被扫描,因为其他分区不能包含任何满足WHERE
子句的记录。见Section 26.4, “Partition Pruning”
,以获取更多信息关于如何实现这一点。
基于RANGE COLUMNS
的分区类型是另一种形式的分区。使用多个列来定义应用于行在分区中放置以及确定特定分区是否包括或排除时的范围值,使得可以实现基于RANGE COLUMNS
的多列分区。见Section 26.2.3.1, “RANGE COLUMNS partitioning”
,以获取更多信息。
基于时间间隔的分区方案。 如果你希望在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
分区,使用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。)Note在MySQL 8.4中,也可以使用
UNIX_TIMESTAMP(timestamp_column)
作为分区表达式,对于以LIST
方式分区的表。然而,这通常不是一个实际的做法。 -
使用
RANGE COLUMNS
进行分区,选择一个以DATE
或DATETIME
类型的列作为分区列。例如,可以直接使用joined
列来定义members
表,如下所示: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 );
除了DATE
或DATETIME
之外的日期或时间类型的分区列,使用RANGE COLUMNS
是不被支持的。