mysql表分区

分区表是指将表按照创建时所指定的规则(如按时间、按范围等)来进行分区,即将一个大表拆分成多个小表,每个小表称为一个分区,并且每个分区可以独立存储于不同的物理介质上,从而在特定的SQL操作中减少数据读写的总量以缩减sql语句的响应时间,同时对于应用来说分区完全是透明的。

表分区的创建

按照 Partitioning Types 分类,大致分为如下几类:

1. RANGE 范围分区

int 数字类型

  • 按 int 数字 range 进行分区
#.按int范围进行分区
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY RANGE (id) (
    PARTITION p1 VALUES LESS THAN (10),
    PARTITION p2 VALUES LESS THAN (20),
    PARTITION p3 VALUES LESS THAN (30),
    PARTITION p4 VALUES LESS THAN MAXVALUE
  );

#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p2 分区只有 aaa 这条记录
SELECT * FROM employees PARTITION (p2) WHERE id < 100 LIMIT 10;

date 日期类型

  • 按 date 日期 range 进行分区,需将其转为数字类型
#.按时间范围进行分区:时间类型 timestamp 本身就是数字
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime timestamp DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY RANGE (UNIX_TIMESTAMP(inserttime)) (
    PARTITION p20180101 VALUES LESS THAN (UNIX_TIMESTAMP('2018-01-02')),
    PARTITION p20190101 VALUES LESS THAN (UNIX_TIMESTAMP('2019-01-02')),
    PARTITION p20200101 VALUES LESS THAN (UNIX_TIMESTAMP('2020-01-02')),
    PARTITION p99991231 VALUES LESS THAN MAXVALUE
);

#.按时间范围进行分区:时间类型 datetime 用 TO_DAYS() 转换为数字
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY RANGE (TO_DAYS(inserttime)) (
    PARTITION p20180101 VALUES LESS THAN (TO_DAYS('2018-01-02')),
    PARTITION p20190101 VALUES LESS THAN (TO_DAYS('2019-01-02')),
    PARTITION p20200101 VALUES LESS THAN (TO_DAYS('2020-01-02')),
    PARTITION p99991231 VALUES LESS THAN MAXVALUE
);

#.按时间范围进行分区:时间类型 datetime 用 YEAR() 转换为数字
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY RANGE (YEAR(inserttime)) (
    PARTITION p20180101 VALUES LESS THAN (2018),
    PARTITION p20190101 VALUES LESS THAN (2019),
    PARTITION p20200101 VALUES LESS THAN (2020),
    PARTITION p99991231 VALUES LESS THAN MAXVALUE
);
  • 通过测试数据进行分区验证
#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p20190101 分区只有 aaa 这条记录
SELECT * FROM employees PARTITION (p20190101) WHERE id < 100 LIMIT 10;

2. LIST 列表分区

#.按list散点值进行分区
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY LIST (id) (
    PARTITION p1 VALUES IN (11,12,13),
    PARTITION p2 VALUES IN (21,22,23),
    PARTITION p3 VALUES IN (31,32,33)
  );

#.只能插入分区指定的的值, 示例不存在 id=41 则插入失败
INSERT INTO employees(id,name,inserttime) VALUES(41,'ccc','2025-04-17 00:00:000');

#.以下插入成功, 因为 id=11 满足 p1 分区
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p1 分区只有 aaa 这条记录
SELECT * FROM employees PARTITION (p1) WHERE id < 100 LIMIT 10;

3. COLUMNS 列分区

RANGE COLUMNS 分区

  • 以下是针对单个字段进行 RANGE COLUMNS 分区:
#.按range范围进行分区:时间类型 datetime 用 COLUMNS ()
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY RANGE COLUMNS (inserttime) (
    PARTITION p20180101 VALUES LESS THAN ('2018-01-02'),
    PARTITION p20190101 VALUES LESS THAN ('2019-01-02'),
    PARTITION p20200101 VALUES LESS THAN ('2020-01-02'),
    PARTITION p99991231 VALUES LESS THAN MAXVALUE
);

#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p20190101 分区只有 aaa 这条记录
SELECT * FROM employees PARTITION (p20190101) WHERE id < 100 LIMIT 10;
  • 以下是针对多个字段进行 RANGE COLUMNS 分区:
#.创建测试表
DROP TABLE IF EXISTS employees;
CREATE TABLE employees (a int, b int, c varchar(4))
  PARTITION BY RANGE COLUMNS(a, b) (
    PARTITION p1 VALUES LESS THAN (5, 12),
    PARTITION p2 VALUES LESS THAN (MAXVALUE, MAXVALUE)
);

#.插入测试数据并验证分区
mysql> INSERT INTO employees VALUES (5,10,'a'), (5,11,'b'), (5,12,'c'),(6,13,'d');
mysql> SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='employees';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p1             |          2 |
| p2             |          2 |
+----------------+------------+

LIST COLUMNS 分区

#.准备测试表
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY LIST COLUMNS (name) (
    PARTITION p1 VALUES IN ('aaa'),
    PARTITION p2 VALUES IN ('bbb'),
    PARTITION p3 VALUES IN ('ccc'),
    PARTITION p4 VALUES IN ('ddd')
);

#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p1 分区只有 aaa 这条记录
SELECT * FROM employees PARTITION (p1) WHERE id < 100 LIMIT 10;

4. HASH 哈希分区

  • 根据 inserttime的年份 进行均匀分布的分区
#.按 hash 进行分区
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY HASH (YEAR(inserttime)) PARTITIONS 4;

#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(41,'ddd','2021-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(51,'eee','2022-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p2 分区有 aaa,eee 这2条记录
SELECT * FROM employees PARTITION (p2) WHERE id < 100 LIMIT 10;

LINEAR HASH 分区

#.按 linear hash 进行分区
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY LINEAR HASH (YEAR(inserttime)) PARTITIONS 4;

#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(41,'ddd','2021-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(51,'eee','2022-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p2 分区有 aaa,eee 这2条记录
SELECT * FROM employees PARTITION (p2) WHERE id < 100 LIMIT 10;

5. KEY 键分区

  • 使用 KEY Partitioning 的前提是该表有 PRIMARY KEY 或 UNIQUE KEY
#.按key进行分区:
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10) NOT NULL PRIMARY KEY, name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY KEY () PARTITIONS 4;

#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p2 分区有 aaa,ccc 这2条记录
SELECT * FROM employees PARTITION (p2) WHERE id < 100 LIMIT 10;

6. SUBPARTITION 子分区

#.按子分区进行分区
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) 
  ENGINE=InnoDB PARTITION BY RANGE( YEAR(inserttime) )
    SUBPARTITION BY HASH( TO_DAYS(inserttime) )
    SUBPARTITIONS 2 (
        PARTITION p1 VALUES LESS THAN (2018),
        PARTITION p2 VALUES LESS THAN (2019),
        PARTITION p3 VALUES LESS THAN MAXVALUE
    );

#.插入测试数据
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
SELECT * FROM employees order by id;

#.查看分区规则及数据分布
SELECT table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name='employees';

#.查看特定分区满足条件的数据,示例 p3 分区有 bbb,ccc 这2条记录
SELECT * FROM employees PARTITION (p3) WHERE id < 100 LIMIT 10;

表分区的维护

1. 查看分区信息

#.指定分区查询
SELECT * FROM employees PARTITION (p2) WHERE id < 100 LIMIT 10; 

#.查看分区信息
EXPLAIN PARTITIONS SELECT * FROM employees;

#.查看分区规则及数据分布
SELECT table_schema,table_name,partition_name,table_rows FROM information_schema.partitions WHERE table_name = 'employees';

#.查看分区规则及数据分布,按时间类型分区 from_unixtime(partition_description) 才有效
SELECT table_schema,table_name,partition_name,partition_expression,from_unixtime(partition_description)
,table_rows,avg_row_length,data_length/1024/1024/1024 as data_size,index_length/1024/1024/1024 as index_size 
FROM information_schema.partitions WHERE table_name = 'employees';

2. 表分区的增删改

#.1.创建基础表.
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS employees(id int(10), name nvarchar(20), inserttime datetime DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB;
INSERT INTO employees(id,name,inserttime) VALUES(11,'aaa','2018-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(21,'bbb','2019-01-31 00:00:000');
INSERT INTO employees(id,name,inserttime) VALUES(31,'ccc','2020-01-31 00:00:000');
SELECT * FROM employees order by id;

#.2.向基础表创建分区(分区必须覆盖所有已存在的数值)
#ALTER TABLE employees PARTITION BY RANGE (id) (PARTITION p10 VALUES LESS THAN (10), PARTITION p20 VALUES LESS THAN (20));
ALTER TABLE employees PARTITION BY RANGE (id) (PARTITION p20 VALUES LESS THAN (20), PARTITION p100 VALUES LESS THAN (100));

#.3.已分区表添加分区(新的分区必须大于已有分区最大值)
ALTER TABLE employees ADD PARTITION (PARTITION p110 VALUES LESS THAN (110));

#.4.插入数据测试(满足分区条件的成功,超出分区范围的失败)
mysql> INSERT INTO employees(id,name,inserttime) VALUES(101,'ddd','2021-01-31 00:00:000');
Query OK, 0 rows affected (0.03 sec)
mysql> INSERT INTO employees(id,name,inserttime) VALUES(201,'eee','2022-01-31 00:00:000');    
ERROR 1526 (HY000): Table has no partition for value 201

#.5.合并多个分区为一个(将 p100,p110 合并为 p200)
ALTER TABLE employees REORGANIZE PARTITION p100,p110 INTO (PARTITION p200 VALUES LESS THAN (200));

#.6.分解一个已有分区(将 p200 分解为 p100, p200)
ALTER TABLE employees REORGANIZE PARTITION p200 INTO (PARTITION p100 VALUES LESS THAN (100), PARTITION p200 VALUES LESS THAN (200));

#.7.特定分区的重建与优化
ALTER TABLE employees REBUILD PARTITION p100, p200;
ALTER TABLE employees OPTIMIZE PARTITION p100, p200;
ALTER TABLE employees ANALYZE PARTITION p100;
ALTER TABLE employees REPAIR PARTITION p100, p200;
ALTER TABLE employees CHECK PARTITION p200;

#.8.删除某个分区,同时删数据
#.对于有大量行的表,TRUNCATE PARTITION 比运行 DELETE FROM employees WHERE inserttime <= '2018-08-01'; 这样的DELETE查询要有效得多
#.但它不会返回删除了多少行,可以通过select count()语句查看
ALTER TABLE employees TRUNCATE PARTITION p200;

#.9.移除所有分区,不删数据
ALTER TABLE employees REMOVE PARTITIONING;

附录

MySQL 使用表分区,需要注意如下几点:

  • 创建分区的时候,必须指定至少一个分区
  • RANGE 处必须为INT型,时间字段用函数转 YEAR()、YEARWEEK()、TO_DAYS()
  • THAN 处必须为INT型,时间字段用函数转 TO_DAYS、TO_SECONDS()、UNIX_TIMESTAMP()
  • 范围分区添加只能在最大值后面追加
  • mysql5.6.7之前,单表最多1024个分区;5.6.7开始,单表最多8192个分区
  • 使用单个分区查询时,查询语句必须指定具体的分区名
  • 当查询语句涉及到多个分区时,MySQL会自动选择需要进行查询的分区
  • 分区表无法使用外键约束
  • NULL值会使分区过滤无效
  • 所有分区必须使用相同的存储引擎

MySQL支持多种类型的分区,每种类型适用于不同的场景:

  • RANGE分区:基于一个列值的范围来分配数据。适用于自增id、时间戳或日期字段
  • LIST分区:基于列值属于预定义列表中的一个值来分配数据
  • HASH分区:根据用户定义的表达式的返回值进行分区,通常用于均匀分布数据
  • KEY分区:类似于HASH分区,但是MySQL系统会自动计算分区键值,适用于主键或唯一键
  • COLUMNS分区:允许使用多列作为分区键,支持RANGE和LIST分区

参考文章:

Copyright © www.sqlfans.cn 2021 - 2026 All Right Reserved更新时间: 2026-08-26 10:24:57

results matching ""

    No results matching ""