Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 112 additions & 108 deletions constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,7 @@ aliases: ['/docs-cn/dev/reference/sql/constraints/']

# 约束

TiDB 支持的基本约束与 MySQL 的基本相同,但有以下区别:

- 默认对唯一约束进行[惰性检查](/transaction-overview.md#事务的惰性检查)。通过在事务提交时再进行批量检查,TiDB 能够减少网络开销、提升性能。您可通过设置 `tidb_constraint_check_in_place` 为 `TRUE` 改变此行为。

- TiDB 支持创建外键约束,但不会在 DML 语句中对外键进行约束(即外键约束不生效)。

## 外键约束

目前,TiDB 支持创建外键。例如:

```sql
CREATE TABLE users (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
doc JSON
);
CREATE TABLE orders (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
doc JSON,
FOREIGN KEY fk_user_id (user_id) REFERENCES users(id)
);
```

{{< copyable "sql" >}}

```sql
SELECT table_name, column_name, constraint_name, referenced_table_name, referenced_column_name
FROM information_schema.key_column_usage WHERE table_name IN ('users', 'orders');
```

```
+------------+-------------+-----------------+-----------------------+------------------------+
| table_name | column_name | constraint_name | referenced_table_name | referenced_column_name |
+------------+-------------+-----------------+-----------------------+------------------------+
| users | id | PRIMARY | NULL | NULL |
| orders | id | PRIMARY | NULL | NULL |
| orders | user_id | fk_user_id | users | id |
+------------+-------------+-----------------+-----------------------+------------------------+
3 rows in set (0.00 sec)
```

TiDB 也支持使用 `ALTER TABLE` 命令来删除外键(`DROP FOREIGN KEY`)和添加外键(`ADD FOREIGN KEY`):

{{< copyable "sql" >}}

```sql
ALTER TABLE orders DROP FOREIGN KEY fk_user_id;
ALTER TABLE orders ADD FOREIGN KEY fk_user_id (user_id) REFERENCES users(id);
```

### 注意

* TiDB 支持外键是为了在将其他数据库迁移到 TiDB 时,不会因为此语法报错。但是,TiDB 不会在 DML 语句中对外键进行约束。例如,即使 `users` 表中不存在 `id=123` 的记录,下列事务也能提交成功:

```
START TRANSACTION;
INSERT INTO orders (user_id, doc) VALUES (123, NULL);
COMMIT;
```

* TiDB 在执行 `SHOW CREATE TABLE` 语句的结果中不显示外键信息。
TiDB 支持的约束与 MySQL 的基本相同。

## 非空约束

Expand Down Expand Up @@ -118,61 +58,67 @@ Query OK, 1 row affected (0.03 sec)

* 第三条 `INSERT` 语句成功,因为 `last_login` 列没有被明确地指定为 `NOT NULL`。默认允许 `NULL` 值。

## 主键约束
## 唯一约束

TiDB 支持的主键约束规则与 MySQL 支持的相似。例如:
TiDB 的乐观事务中,默认会对唯一约束进行[惰性检查](/transaction-overview.md#事务的惰性检查)。通过在事务提交时再进行批量检查,TiDB 能够减少网络开销、提升性能。例如:

{{< copyable "sql" >}}

```sql
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY);
```

```
Query OK, 0 rows affected (0.12 sec)
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(60) NOT NULL,
UNIQUE KEY (username)
);
INSERT INTO users (username) VALUES ('dave'), ('sarah'), ('bill');
```

{{< copyable "sql" >}}

```sql
CREATE TABLE t2 (a INT NULL PRIMARY KEY);
START TRANSACTION;
```

```
ERROR 1171 (42000): All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead
Query OK, 0 rows affected (0.00 sec)
```

{{< copyable "sql" >}}

```sql
CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY, b INT NOT NULL PRIMARY KEY);
INSERT INTO users (username) VALUES ('jane'), ('chris'), ('bill');
```

```
ERROR 1068 (42000): Multiple primary key defined
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
```

{{< copyable "sql" >}}

```sql
CREATE TABLE t4 (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY (a,b));
INSERT INTO users (username) VALUES ('steve'),('elizabeth');
```

```
Query OK, 0 rows affected (0.10 sec)
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
```

* 表 `t2` 创建失败,因为定义为主键的列 `a` 不能允许 `NULL` 值。
* 表 `t3` 创建失败,因为一张表只能有一个主键。
* 表 `t4` 创建成功,因为虽然只能有一个主键,但 TiDB 支持定义一个多列组合作为复合主键。
{{< copyable "sql" >}}

除上述规则外,TiDB 还强加了另一个限制,即一旦一张表创建成功,其主键就不能再改变。
```sql
COMMIT;
```

## 唯一约束
```
ERROR 1062 (23000): Duplicate entry 'bill' for key 'username'
```

在 TiDB 中,默认会对唯一约束进行惰性检查。通过直到事务提交时才进行批量检查,TiDB 能够减少网络通信开销。例如:
第一条 `INSERT` 语句不会导致重复键错误,这同 MySQL 的规则一致。该检查将推迟到事务提交时才会进行。

{{< copyable "sql" >}}
你可通过设置 `tidb_constraint_check_in_place` 为 `1` 停用此行为(该变量设置对悲观事务无效,悲观事务始终在语句执行时检查约束)。如果停用此行为,则会在执行语句时就对唯一约束进行检查。例如:

```sql
DROP TABLE IF EXISTS users;
Expand All @@ -187,7 +133,7 @@ INSERT INTO users (username) VALUES ('dave'), ('sarah'), ('bill');
{{< copyable "sql" >}}

```sql
START TRANSACTION;
SET tidb_constraint_check_in_place = 1;
```

```
Expand All @@ -197,78 +143,136 @@ Query OK, 0 rows affected (0.00 sec)
{{< copyable "sql" >}}

```sql
INSERT INTO users (username) VALUES ('jane'), ('chris'), ('bill');
START TRANSACTION;
```

```
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.00 sec)
```

{{< copyable "sql" >}}

```sql
INSERT INTO users (username) VALUES ('steve'),('elizabeth');
INSERT INTO users (username) VALUES ('jane'), ('chris'), ('bill');
```

```
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
ERROR 1062 (23000): Duplicate entry 'bill' for key 'username'
..
```

第一条 `INSERT` 语句导致了重复键错误。这会造成额外的网络通信开销,并可能降低插入操作的吞吐量。

## 主键约束

与 MySQL 行为一样,主键约束包含了唯一约束,即创建了主键约束相当于拥有了唯一约束。此外,TiDB 其他的主键约束规则也与 MySQL 相似。例如:

{{< copyable "sql" >}}

```sql
COMMIT;
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY);
```

```
ERROR 1062 (23000): Duplicate entry 'bill' for key 'username'
Query OK, 0 rows affected (0.12 sec)
```

* 第一条 `INSERT` 语句不会导致重复键错误,这同 MySQL 的规则一致。该检查将推迟到事务提交时才会进行。

如果将 `tidb_constraint_check_in_place` 更改为 `TRUE`,则会在执行语句时就对唯一约束进行检查。例如:
{{< copyable "sql" >}}

```sql
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(60) NOT NULL,
UNIQUE KEY (username)
);
INSERT INTO users (username) VALUES ('dave'), ('sarah'), ('bill');
CREATE TABLE t2 (a INT NULL PRIMARY KEY);
```

```
ERROR 1171 (42000): All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead
```

{{< copyable "sql" >}}

```sql
SET tidb_constraint_check_in_place = TRUE;
CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY, b INT NOT NULL PRIMARY KEY);
```

```
Query OK, 0 rows affected (0.00 sec)
ERROR 1068 (42000): Multiple primary key defined
```

{{< copyable "sql" >}}

```sql
START TRANSACTION;
CREATE TABLE t4 (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY (a,b));
```

```
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.10 sec)
```

分析:

* 表 `t2` 创建失败,因为定义为主键的列 `a` 不能允许 `NULL` 值。
* 表 `t3` 创建失败,因为一张表只能有一个主键。
* 表 `t4` 创建成功,因为虽然只能有一个主键,但 TiDB 支持定义一个多列组合作为复合主键。

除上述规则外,默认情况下,TiDB 还有一个额外限制,即一旦一张表创建成功,其主键就不能再改变。如果需要添加/删除主键,需要在 TiDB 配置文件中将 `alter-primary-key` 设置为 `true`,并重启 TiDB 实例使之生效。

当开启添加/删除主键功能以后,TiDB 允许对表添加/删除主键。但需要注意的是,对于在未开启该功能时创建的整数类型的主键的表,即使开启添加/删除主键功能,也不能删除其主键约束。

## 外键约束

> **注意:**
>
> TiDB 仅部分支持外键约束功能。

TiDB 支持创建外键约束。例如:

```sql
CREATE TABLE users (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
doc JSON
);
CREATE TABLE orders (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
doc JSON,
FOREIGN KEY fk_user_id (user_id) REFERENCES users(id)
);
```

{{< copyable "sql" >}}

```sql
INSERT INTO users (username) VALUES ('jane'), ('chris'), ('bill');
SELECT table_name, column_name, constraint_name, referenced_table_name, referenced_column_name
FROM information_schema.key_column_usage WHERE table_name IN ('users', 'orders');
```

```
ERROR 1062 (23000): Duplicate entry 'bill' for key 'username'
..
+------------+-------------+-----------------+-----------------------+------------------------+
| table_name | column_name | constraint_name | referenced_table_name | referenced_column_name |
+------------+-------------+-----------------+-----------------------+------------------------+
| users | id | PRIMARY | NULL | NULL |
| orders | id | PRIMARY | NULL | NULL |
| orders | user_id | fk_user_id | users | id |
+------------+-------------+-----------------+-----------------------+------------------------+
3 rows in set (0.00 sec)
```

TiDB 也支持使用 `ALTER TABLE` 命令来删除外键(`DROP FOREIGN KEY`)和添加外键(`ADD FOREIGN KEY`):

{{< copyable "sql" >}}

```sql
ALTER TABLE orders DROP FOREIGN KEY fk_user_id;
ALTER TABLE orders ADD FOREIGN KEY fk_user_id (user_id) REFERENCES users(id);
```

* 第一条 `INSERT` 语句导致了重复键错误。这会造成额外的网络通信开销,并可能降低插入操作的吞吐量。
### 注意

* TiDB 支持外键是为了在将其他数据库迁移到 TiDB 时,不会因为此语法报错。但是,TiDB 不会在 DML 语句中对外键进行约束检查。例如,即使 `users` 表中不存在 `id=123` 的记录,下列事务也能提交成功:

```
START TRANSACTION;
INSERT INTO orders (user_id, doc) VALUES (123, NULL);
COMMIT;
```

* TiDB 在执行 `SHOW CREATE TABLE` 语句的结果中不显示外键信息。