Skip to content

Commit

Permalink
auto-increment: change "continuous" to monotonic (pingcap#4449)
Browse files Browse the repository at this point in the history
* auto-increment: change "continuous" to monotonic

* Update auto-increment.md

Co-authored-by: Keke Yi <[email protected]>

* align pingcap#3860

Co-authored-by: Keke Yi <[email protected]>
  • Loading branch information
TomShawn and yikeke authored Sep 24, 2020
1 parent 3e1408c commit 1d584a2
Showing 1 changed file with 112 additions and 28 deletions.
140 changes: 112 additions & 28 deletions auto-increment.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ aliases: ['/docs-cn/dev/auto-increment/']

## 基本概念

`AUTO_INCREMENT` 是用于自动填充缺省列值的列属性。当 `INSERT` 语句没有指定 `AUTO_INCREMENT` 列的具体值时,系统会自动地为该列分配一个值。该值满足唯一性,以及**特殊情况下**的递增性和连续性。使用示例如下:
`AUTO_INCREMENT` 是用于自动填充缺省列值的列属性。当 `INSERT` 语句没有指定 `AUTO_INCREMENT` 列的具体值时,系统会自动地为该列分配一个值。

出于性能原因,自增编号是系统批量分配给每台 TiDB 服务器的值(默认 3 万个值),因此自增编号能保证唯一性,但分配给 `INSERT` 语句的值仅在单台 TiDB 服务器上具有单调性。

{{< copyable "sql" >}}

Expand Down Expand Up @@ -94,53 +96,135 @@ insert into t (c) values (1)
1. 客户端向实例 B 插入一条将 `id` 设置为 `2` 的语句 `insert into t values (2, 1)`,并执行成功。
2. 客户端向实例 A 发送 `Insert` 语句 `insert into t (c) (1)`,这条语句中没有指定 `id` 的值,所以会由 A 分配。当前 A 缓存了 `[1, 30000]` 这段 ID,可能会分配 `2` 为自增 ID 的值,并把本地计数器加 `1`。而此时数据库中已经存在 `id``2` 的数据,最终返回 `Duplicated Error` 错误。

### 递增性保证
### 单调性保证

`AUTO_INCREMENT` 列隐式分配值的递增性只能在含有单个 TiDB 实例的集群中得到保证:即对于同一个自增列,先分配的值小于后分配的值;而在含有多个 TiDB 实例的集群中,无法保证自增列的递增性。
TiDB 保证 `AUTO_INCREMENT` 自增值在单台服务器上单调递增。以下示例在一台服务器上生成连续的 `AUTO_INCREMENT` 自增值 `1`-`3`

例如,对于上述例子,如果先向实例 B 执行一条插入语句,再向实例 A 执行一条插入语句。根据缓存自增 ID 的性质,自增列隐式分配的值可能分别是 `30002``2`。因此从时间上看,不满足递增性。
{{< copyable "sql" >}}

### 连续性保证
```sql
CREATE TABLE t (a int primary key AUTO_INCREMENT, b timestamp NOT NULL DEFAULT NOW());
INSERT INTO t (a) VALUES (NULL), (NULL), (NULL);
SELECT * FROM t;
```

在含有多个 TiDB 实例的集群中,`AUTO_INCREMENT` 分配值的连续性**只能**在 batch insert 语句中得到保证。
```sql
Query OK, 0 rows affected (0.11 sec)
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0
+---+---------------------+
| a | b |
+---+---------------------+
| 1 | 2020-09-09 20:38:22 |
| 2 | 2020-09-09 20:38:22 |
| 3 | 2020-09-09 20:38:22 |
+---+---------------------+
3 rows in set (0.00 sec)
```

例如,对以下表执行以下语句:
如果在另一台服务器上执行插入操作,那么 `AUTO_INCREMENT` 值的顺序可能会剧烈跳跃,这是由于每台服务器都有各自缓存的 `AUTO_INCREMENT` 自增值。

{{< copyable "sql" >}}

```sql
create table t (a int primary key AUTO_INCREMENT)
INSERT INTO t (a) VALUES (NULL);
SELECT * FROM t;
```

```sql
insert into t values (), (), (), ()
Query OK, 1 row affected (0.03 sec)

+---------+---------------------+
| a | b |
+---------+---------------------+
| 1 | 2020-09-09 20:38:22 |
| 2 | 2020-09-09 20:38:22 |
| 3 | 2020-09-09 20:38:22 |
| 2000001 | 2020-09-09 20:43:43 |
+---------+---------------------+
4 rows in set (0.00 sec)
```

即使存在正在执行并发写入的其他 TiDB 实例,或者当前实例剩余的缓存 ID 数量不够,都不会影响分配值的连续性。
以下示例在最先的一台服务器上执行一个插入 `INSERT` 操作,生成 `AUTO_INCREMENT``4`。因为这台服务器上仍有剩余的 `AUTO_INCREMENT` 缓存值可用于分配。在该示例中,值的顺序不具有全局单调性:

### `_tidb_rowid` 的关联性
```sql
mysql> INSERT INTO t (a) VALUES (NULL);
Query OK, 1 row affected (0.01 sec)

> **注意:**
>
> 在没有指定整数类型主键的情况下 TiDB 会使用 `_tidb_rowid` 来标识行,该数值的分配会和自增列(如果存在的话)共用一个分配器,其中缓存的大小可能会被自增列和 `_tidb_rowid` 共同消耗。因此会有以下的示例情况:
mysql> SELECT * FROM t ORDER BY b;
+---------+---------------------+
| a | b |
+---------+---------------------+
| 1 | 2020-09-09 20:38:22 |
| 2 | 2020-09-09 20:38:22 |
| 3 | 2020-09-09 20:38:22 |
| 2000001 | 2020-09-09 20:43:43 |
| 4 | 2020-09-09 20:44:43 |
+---------+---------------------+
5 rows in set (0.00 sec)
```

`AUTO_INCREMENT` 缓存不会持久化,重启会导致缓存值失效。以下示例中,最先的一台服务器重启后,向该服务器执行一条插入操作:

```sql
mysql> INSERT INTO t (a) VALUES (NULL);
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM t ORDER BY b;
+---------+---------------------+
| a | b |
+---------+---------------------+
| 1 | 2020-09-09 20:38:22 |
| 2 | 2020-09-09 20:38:22 |
| 3 | 2020-09-09 20:38:22 |
| 2000001 | 2020-09-09 20:43:43 |
| 4 | 2020-09-09 20:44:43 |
| 2030001 | 2020-09-09 20:54:11 |
+---------+---------------------+
6 rows in set (0.00 sec)
```

TiDB 服务器频繁重启可能导致 `AUTO_INCREMENT` 缓存值被快速消耗。在以上示例中,最先的一台服务器本来有可用的缓存值 `[5-3000]`。但重启后,这些值便丢失了,无法进行重新分配。

用户不应指望 `AUTO_INCREMENT` 值保持连续。在以下示例中,一台 TiDB 服务器的缓存值为 `[2000001-2030000]`。当手动插入值 `2029998` 时,TiDB 取用了一个新缓存区间的值:

```sql
mysql> create table t(id int unique key AUTO_INCREMENT);
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO t (a) VALUES (2029998);
Query OK, 1 row affected (0.01 sec)

mysql> insert into t values (),(),();
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> INSERT INTO t (a) VALUES (NULL);
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO t (a) VALUES (NULL);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t (a) VALUES (NULL);
Query OK, 1 row affected (0.02 sec)

mysql> select _tidb_rowid, id from t;
+-------------+------+
| _tidb_rowid | id |
+-------------+------+
| 4 | 1 |
| 5 | 2 |
| 6 | 3 |
+-------------+------+
3 rows in set (0.01 sec)
mysql> INSERT INTO t (a) VALUES (NULL);
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM t ORDER BY b;
+---------+---------------------+
| a | b |
+---------+---------------------+
| 1 | 2020-09-09 20:38:22 |
| 2 | 2020-09-09 20:38:22 |
| 3 | 2020-09-09 20:38:22 |
| 2000001 | 2020-09-09 20:43:43 |
| 4 | 2020-09-09 20:44:43 |
| 2030001 | 2020-09-09 20:54:11 |
| 2029998 | 2020-09-09 21:08:11 |
| 2029999 | 2020-09-09 21:08:11 |
| 2030000 | 2020-09-09 21:08:11 |
| 2060001 | 2020-09-09 21:08:11 |
| 2060002 | 2020-09-09 21:08:11 |
+---------+---------------------+
11 rows in set (0.00 sec)
```

以上示例插入 `2030000` 后,下一个值为 `2060001`,即顺序出现跳跃。这是因为另一台 TiDB 服务器获取了中间缓存区间 `[2030001-2060000]`。当部署有多台 TiDB 服务器时,`AUTO_INCREMENT` 值的顺序会出现跳跃,因为对缓存值的请求是交叉出现的。

### 缓存大小控制

TiDB 自增 ID 的缓存大小在早期版本中是对用户透明的。从 v3.1.2、v3.0.14 和 v4.0.rc-2 版本开始,TiDB 引入了 `AUTO_ID_CACHE` 表选项来允许用户自主设置自增 ID 分配缓存的大小。例如:
Expand Down

0 comments on commit 1d584a2

Please sign in to comment.