Skip to content

Commit

Permalink
address comment
Browse files Browse the repository at this point in the history
  • Loading branch information
tiancaiamao committed Jun 24, 2019
1 parent 813d69d commit 2459d8b
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 59 deletions.
1 change: 1 addition & 0 deletions TOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
- [实用工具语句](dev/reference/sql/statements/util.md)
- [约束](dev/reference/sql/constraints.md)
- [生成列](dev/reference/sql/generated-columns.md)
- [分区表](dev/reference/sql/partitioning.md)
- [字符集](dev/reference/sql/character-set.md)
+ 配置
+ tidb-server
Expand Down
124 changes: 65 additions & 59 deletions dev/reference/sql/partitioning.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
---
title: 分区表
category: reference
aliases: ['/docs-cn/sql/partitioning/']
---

# 分区类型
## 分区类型

## Range 分区
本节介绍在 TiDB 中的分区的类型。当前支持的类型包括 Range 分区和 Hash 分区。Range 分区可以用于解决业务中大量删除带来的性能问题,支持快速删除分区。Hash 分区则可以用于大量写入场景下的数据打散。

一个表按 range 分区是指,对于表的每个分区中包含的所有行,按分区表达式计算的值都落在给定的范围内。
Range 必须是连续的,并且不能有重叠,通过使用 `VALUES LESS THAN` 操作进行定义。接下来的例子中,假设你要创建一个人事记录的表:
### Range 分区

一个表按 range 分区是指,对于表的每个分区中包含的所有行,按分区表达式计算的值都落在给定的范围内。Range 必须是连续的,并且不能有重叠,通过使用 `VALUES LESS THAN` 操作进行定义。

下列场景中,假设你要创建一个人事记录的表:

```SQL
CREATE TABLE employees (
Expand All @@ -23,7 +25,7 @@ CREATE TABLE employees (
);
```

根据你的需求,可以按各种方式进行 range 分区。其中一种方式是按 `store_id` 列进行分区。你可以这样做:
你可以根据需求按各种方式进行 range 分区。其中一种方式是按 `store_id` 列进行分区。你可以这样做:

```SQL
CREATE TABLE employees (
Expand All @@ -43,7 +45,7 @@ PARTITION BY RANGE (store_id) (
);
```

在这个分区模式中,所有在 `store_id` 为 1 到 5 的员工,都存储在分区 `p0` 里面,`store_id` 为 6 到 10 的员工则存储在分区 `p1` 里面。range 分区要求,分区的定义必须是有序的,从小到大
在这个分区模式中,所有 `store_id` 为 1 到 5 的员工,都存储在分区 `p0` 里面,`store_id` 为 6 到 10 的员工则存储在分区 `p1` 里面。range 分区要求,分区的定义必须是有序的,按从小到大递增

新插入一行数据 `(72, 'Mitchell', 'Wilson', '1998-06-25', NULL, 13)` 将会落到分区 `p2` 里面。但如果你插入一条 `store_id` 大于 20 的记录,则会报错,因为 TiDB 无法知晓应该将它插入到哪个分区。这种情况下,可以在建表时使用最大值:

Expand Down Expand Up @@ -90,7 +92,7 @@ PARTITION BY RANGE (job_code) (

除了可以按 `store_id` 切分,你还可以按日期切分。例如,假设按员工离职的年份进行分区:

```
```SQL
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
Expand All @@ -108,9 +110,9 @@ PARTITION BY RANGE ( YEAR(separated) ) (
);
```

在 range 分区中,可以基于 `timestamp` 列的值分区,使用 `unix_timestamp()` 函数,下面是一个例子
在 range 分区中,可以基于 `timestamp` 列的值分区,使用 `unix_timestamp()` 函数,例如

```
```SQL
CREATE TABLE quarterly_report_status (
report_id INT NOT NULL,
report_status VARCHAR(20) NOT NULL,
Expand All @@ -136,15 +138,15 @@ Range 分区在下列条件之一或者多个都满足时,尤其有效:

* 删除旧数据。如果你使用之前的 `employees` 表的例子,你可以简单使用 `ALTER TABLE employees DROP PARTITION p0;` 删除所有在 1991 年以前停止继续在这家公司工作的员工记录。这会比使用 `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;`,TiDB 可以迅速确定,只需要扫描 `p2` 分区的数据,因为其它的分区不满足 `where` 条件。
* 频繁查询分区使用的列。例如执行这样的查询 `EXPLAIN SELECT COUNT(*) FROM employees WHERE separated BETWEEN '2000-01-01' AND '2000-12-31' GROUP BY store_id;`,TiDB 可以迅速确定,只需要扫描 `p2` 分区的数据,因为其它的分区不满足 `where` 条件。

## Hash 分区
### Hash 分区

Hash 分区主要用于保证数据均匀地分散到一定数量的分区里面。在 range 分区中你必须为每个分区指定值的范围;在 hash 分区中,你只需要指定分区的数量。

使用 hash 分区时,需要在 `CREATE TABLE` 后面添加 `PARTITION BY HASH (expr)`,其中 `expr` 是一个返回整数的表达式。它可以是一个列名,如果这一列的类型是整数类型。此外,你大概还需要加上 `PARTITIONS num`,其中 `num` 是一个正整数,表示将表划分多少分区。
使用 hash 分区时,需要在 `CREATE TABLE` 后面添加 `PARTITION BY HASH (expr)`,其中 `expr` 是一个返回整数的表达式。它可以是一个列名,如果这一列的类型是整数类型。此外,你很可能还需要加上 `PARTITIONS num`,其中 `num` 是一个正整数,表示将表划分多少分区。

下面的语句创建一个 hash 分区表,按 `store_id` 分成 4 个分区:
下面的语句将创建一个 hash 分区表,按 `store_id` 分成 4 个分区:

```
CREATE TABLE employees (
Expand Down Expand Up @@ -178,20 +180,20 @@ PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;
```

最高效的 hash 函数是作用在单列上,并且函数的单调性是跟列的值一样递增或者递减的,因为这种情况可以像 range 分区一样裁剪。
最高效的 hash 函数是作用在单列上,并且函数的单调性是跟列的值是一样递增或者递减的,因为这种情况可以像 range 分区一样裁剪。

例如 `date_col` 是类型为 `DATE` 的列,表达式 `TO_DAYS(date_col)` 的值是直接随 `date_col` 的值变化的。`YEAR(date_col)``TO_DAYS(date_col)` 就不太一样,因为不是每次 `date_col` 变化时 `YEAR(date_col)` 都会得到不同的值。即使如此,`YEAR(date_col)` 也仍然是一个比如好的 hash 函数,因为它的结果是随着 `date_col` 的值的比例变化的。
例如 `date_col` 是类型为 `DATE` 的列,表达式 `TO_DAYS(date_col)` 的值是直接随 `date_col` 的值变化的。`YEAR(date_col)``TO_DAYS(date_col)` 就不太一样,因为不是每次 `date_col` 变化时 `YEAR(date_col)` 都会得到不同的值。即使如此,`YEAR(date_col)` 也仍然是一个比如好的 hash 函数,因为它的结果是随着 `date_col` 的值的比例变化的。

作为对比,假设我们有一个类型是 INT 的 `int_col` 的列。考虑一下这个表达式 `POW(5-int_col,3) + 6`,这并不是一个比较好的 hash 函数,因为 `int_col` 的值的变化,变化会让表达式的结果成比例地变化。改变 `int_col` 的值会使表达式的结果的值变化巨大。例如,`int_col` 从 5 变到 6 表达式的结果变化是 -1,但是从 6 变到 7 的时候表达式的值的变化是 -7。
作为对比,假设我们有一个类型是 INT 的 `int_col` 的列。考虑一下表达式 `POW(5-int_col,3) + 6`,这并不是一个比较好的 hash 函数,因为随着 `int_col` 的值的变化,表达式的结果不会成比例地变化。改变 `int_col` 的值会使表达式的结果的值变化巨大。例如,`int_col` 从 5 变到 6 表达式的结果变化是 -1,但是从 6 变到 7 的时候表达式的值的变化是 -7。

总而言之,表达式越接近 `y = cx` 的形式,它越是适合作为 hash 函数。因为表达式越是非线性的,在各个分区上面的数据的分布越是倾向于不均匀。

理论上,hash 分区也是可以做分区裁剪的。而实际上对于多列的情况,实现很难并且计算很耗时。因此,不推荐 hash 分区在表达式中涉及多列。

使用 `PARTITIION BY HASH` 的时候,TiDB 通过表达式的结果做“取余”运算,决定数据落在哪个分区。换句话说,如果分区表达式是 `expr`,分区数是 `num`,则由 `MOD(expr, num)` 决定存储的分区。假设 `t1` 定义如下:

```
`CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
```SQL
CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
PARTITION BY HASH( YEAR(col3) )
PARTITIONS 4;
```
Expand All @@ -204,11 +206,11 @@ MOD(YEAR('2005-09-01'),4)
= 1
```

## 分区对 NULL 值的处理
### 分区对 NULL 值的处理

TiDB 允许计算结果为 NULL 的分区表达式。注意,NULL 不是一个整数类型,NULL 小于所有的整数类型值,正如 `ORDER BY** 的规则一样。
TiDB 允许计算结果为 NULL 的分区表达式。注意,NULL 不是一个整数类型,NULL 小于所有的整数类型值,正如 `ORDER BY` 的规则一样。

### Range 分区对 NULL 的处理
#### Range 分区对 NULL 的处理

如果插入一行到 range 分区表,它的分区列的计算结果是 NULL,那么这一行会被插入到最小的那个分区。

Expand Down Expand Up @@ -249,7 +251,7 @@ mysql> select * from t1;
Empty set (0.00 sec)
```

### Hash 分区对 NULL 的处理
#### Hash 分区对 NULL 的处理

在 Hash 分区中 NULL 值的处理有所不同,如果分区表达式的计算结果为 NULL,它会被当作 0 值处理。

Expand Down Expand Up @@ -280,11 +282,11 @@ Empty set (0.00 sec)

可以看到,插入的记录 `(NULL, 'mothra')``(0, 'gigan')` 落在了同一个分区。

# 分区管理
## 分区管理

通过`ALTER TABLE` 语句可以执行一些添加,删除,合并,切分,重定义分区的操作。
通过 `ALTER TABLE` 语句可以执行一些添加、删除、合并、切分、重定义分区的操作。

## Range 分区管理
### Range 分区管理

创建分区表:

Expand All @@ -304,7 +306,7 @@ PARTITION BY RANGE( YEAR(dob) ) (

删除分区:

```
```SQL
mysql> ALTER TABLE members DROP PARTITION p2;
Query OK, 0 rows affected (0.03 sec)
```
Expand All @@ -316,11 +318,15 @@ mysql> ALTER TABLE members TRUNCATE PARTITION p1;
Query OK, 0 rows affected (0.03 sec)
```

`ALTER TABLE ... REORGANIZE PARTITION` 在 TiDB 中暂不支持
> **注意:**
>
> `ALTER TABLE ... REORGANIZE PARTITION` 在 TiDB 中暂不支持。
添加分区:

`ALTER TABLE members ADD PARTITION (PARTITION p3 VALUES LESS THAN (2010));`
```SQL
ALTER TABLE members ADD PARTITION (PARTITION p3 VALUES LESS THAN (2010));
```

Range 分区中,`ADD PARTITION` 只能在分区列表的最后面添加,如果是添加到已存在的分区范围则会报错:

Expand All @@ -332,17 +338,19 @@ ERROR 1463 (HY000): VALUES LESS THAN value must be strictly »
increasing for each partition
```

## Hash 分区管理
### Hash 分区管理

跟 Range 分区不同,Hash 分区不能够 `DROP PARTITION`

目前 TiDB 的实现暂时不支持 `ALTER TABLE ... COALESCE PARTITION`

# 分区裁剪
## 分区裁剪

有一个优化叫做“分区裁剪”,它是基于一个非常简单的概念:不需要扫描那些匹配不上的分区。假设创建一个分区表 `t1`
有一个优化叫做“分区裁剪”,它基于一个非常简单的概念:不需要扫描那些匹配不上的分区。

```
假设创建一个分区表 `t1`

```SQL
CREATE TABLE t1 (
fname VARCHAR(50) NOT NULL,
lname VARCHAR(50) NOT NULL,
Expand All @@ -359,25 +367,24 @@ PARTITION BY RANGE( region_code ) (

如果你想获得这个 select 语句的结果:

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

很显然,结果必然是在分区 `p0` 或者 `p3` 里面,也就是说,我们只需要在 `p0``p3` 里面去搜索匹配的行。去掉不必要的分区就是所谓的裁剪。优化器如果能裁剪掉一部分的分区,则执行会快于相同的查询去处理整个不做分区的表
很显然,结果必然是在分区 `p0` 或者 `p3` 里面,也就是说,我们只需要在 `p0``p3` 里面去搜索匹配的行。去掉不必要的分区就是所谓的裁剪。优化器如果能裁剪掉一部分的分区,则执行会快于处理整个不做分区的表的相同查询

优化器可以通过 where 条件裁剪的两个场景:

* partition_column = constant
* partition_column IN (constant1, constant2, ..., constantN)

## 分区选择

# 分区选择
SELECT 语句中支持分区选择。实现通过使用一个 `PARTITION` 选项实现。

SELECT 语句中支持分区选择。实现通过使用一个 `PARTITION` 选项。

```
```SQL
CREATE TABLE employees (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
fname VARCHAR(25) NOT NULL,
Expand All @@ -404,7 +411,6 @@ INSERT INTO employees VALUES
('', 'Mark', 'Morgan', 3, 3), ('', 'Karen', 'Cole', 3, 2);
```


你可以查看存储在分区 `p1` 中的行:

```
Expand All @@ -423,7 +429,7 @@ mysql> SELECT * FROM employees PARTITION (p1);

如果希望获得多个分区中的行,可以提供分区名的列表,用逗号隔开。例如,`SELECT * FROM employees PARTITION (p1, p2)` 返回分区 `p1``p2` 的所有行。

使用分区选择时,仍然可以使用 where 条件,ORDER BY 和 LIMIT 等选项。并且使用 HAVING 和 GROUP BY 等聚合选项也是可以的
使用分区选择时,仍然可以使用 where 条件,以及 ORDER BY 和 LIMIT 等选项。使用 HAVING 和 GROUP BY 等聚合选项也是支持的

```
mysql> SELECT * FROM employees PARTITION (p0, p2)
Expand Down Expand Up @@ -462,21 +468,21 @@ mysql> SELECT store_id, COUNT(department_id) AS c

分支选择支持所有类型的分区表,无论是 range 分区或是 hash 分区等。对于 hash 分区,如果没有指定分区名,会自动使用 `p0,p1,p2...pN-1** 作为分区名。

`INSERT ... SELECT**`SELECT** 中也是可以使用分区选择的。
`INSERT ... SELECT``SELECT` 中也是可以使用分区选择的。

# 分区的约束和限制
## 分区的约束和限制

这一节介绍当前 TiDB 分区表的一些约束和限制。
本节介绍当前 TiDB 分区表的一些约束和限制。

## 分区键,主键和唯一键
### 分区键,主键和唯一键

这一节讨论分区键,主键和唯一键之间的关系。一句话总结它们之间的关系要满足的规则:**分区表的每个唯一列,必须包含分区表达式中用到的所有列**
本节讨论分区键,主键和唯一键之间的关系。一句话总结它们之间的关系要满足的规则:**分区表的每个唯一列,必须包含分区表达式中用到的所有列**

> every unique key on the table must use every column in the table's partitioning expression
这里所指的唯一也包含了主键,因为根据主键的定义,主键必须是唯一的。举个例子,下面这些建表语句就是无效的:
这里所指的唯一也包含了主键,因为根据主键的定义,主键必须是唯一的。例如,下面这些建表语句就是无效的:

```
```SQL
CREATE TABLE t1 (
col1 INT NOT NULL,
col2 DATE NOT NULL,
Expand All @@ -499,11 +505,11 @@ PARTITION BY HASH(col1 + col3)
PARTITIONS 4;
```

它们都是有唯一键没有包含所有分区键的
它们都是有唯一键但没有包含所有分区键的

下面是一些合法的语句的例子:

```
```SQL
CREATE TABLE t1 (
col1 INT NOT NULL,
col2 DATE NOT NULL,
Expand All @@ -525,9 +531,9 @@ PARTITION BY HASH(col1 + col3)
PARTITIONS 4;
```

这个例子会产生一个报错
下例中会产生一个报错

```
```SQL
mysql> CREATE TABLE t3 (
-> col1 INT NOT NULL,
-> col2 DATE NOT NULL,
Expand All @@ -545,7 +551,7 @@ ERROR 1491 (HY000): A PRIMARY KEY must include all columns in the table's partit
下面这个表就没法做分区了,因为无论如何都不可能找到满足条件的分区键:
```
```SQL
CREATE TABLE t4 (
col1 INT NOT NULL,
col2 INT NOT NULL,
Expand All @@ -558,7 +564,7 @@ CREATE TABLE t4 (
根据定义,主键也是唯一键,下面两个建表语句是无效的:
```
```SQL
CREATE TABLE t5 (
col1 INT NOT NULL,
col2 DATE NOT NULL,
Expand All @@ -583,11 +589,11 @@ PARTITIONS 4;
两个例子中,主键都没有包含分区表达式中的全部的列。
如果既没有主键,也没有唯一键,则这个限制就没有了
如果既没有主键,也没有唯一键,则不存在这个限制
DDL 变更时,添加唯一索引也需要考虑到这个限制。比如创建了这样一个表:
```
```SQL
mysql> CREATE TABLE t_no_pk (c1 INT, c2 INT)
-> PARTITION BY RANGE(c1) (
-> PARTITION p0 VALUES LESS THAN (10),
Expand All @@ -600,7 +606,7 @@ Query OK, 0 rows affected (0.12 sec)
通过 `ALTER TABLE` 添加非唯一索引是可以的。但是添加唯一索引时,唯一索引里面必须包含 `c1` 列。
## 关于函数的分区限制
### 关于函数的分区限制
只有以下函数可以用于分区表达式:
Expand Down Expand Up @@ -630,7 +636,7 @@ YEAR()
YEARWEEK()
```
## 兼容性
### 兼容性
目前 TiDB 里面只实现了 Range 分区和 Hash 分区,其它的 MySQL 分区类型比如 List 分区和 Key 分区尚不支持。
Expand Down Expand Up @@ -665,7 +671,7 @@ Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0
```
TiDB 每次返回结果会不同
TiDB 每次返回结果会不同,例如:
```
mysql> select * from t;
Expand Down

0 comments on commit 2459d8b

Please sign in to comment.