# 外键与级联

URL: https://caijiao.org/postgresql/advanced/constraints/foreign
Source: docs/postgresql/advanced/constraints/foreign.md
Description: 介绍 PostgreSQL 中的外键与级联操作，帮助开发者理解如何维护表之间的关系，并确保数据一致性。

在 PostgreSQL 中，外键（Foreign Key）用于建立两个表之间的关系。外键约束可以确保数据的一致性和完整性，防止出现无效的数据行。级联操作（Cascade）是外键约束中的一个重要特性，用于定义当父表数据发生变化时，子表应采取的行为。

## 外键 (Foreign Key)

外键约束确保子表中的数据与父表中的数据一致。外键列中的每个值都必须引用父表中的某个有效值。

### 创建外键约束

在创建表时，可以使用 `FOREIGN KEY` 约束来定义外键。以下是一个例子，表示 `orders` 表中的 `customer_id` 列引用 `customers` 表中的 `id` 列：

```sql
CREATE TABLE customers (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100)
);

CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT,
  amount DECIMAL,
  FOREIGN KEY (customer_id) REFERENCES customers(id)
);
````

在这个例子中，`orders` 表中的 `customer_id` 列是外键，引用了 `customers` 表的 `id` 列。

### 外键约束的特性

* **数据一致性**：外键约束确保子表的列值必须与父表中的数据一致。
* **删除与更新行为**：外键约束可以定义在父表数据删除或更新时，子表应如何响应。

### 修改外键约束

如果需要修改外键约束，可以使用 `ALTER TABLE`：

```sql
ALTER TABLE orders
ADD CONSTRAINT fk_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);
```

### 删除外键约束

可以使用以下命令删除外键约束：

```sql
ALTER TABLE orders
DROP CONSTRAINT fk_customer;
```

## 级联操作 (Cascade)

级联操作定义了在父表数据发生变化时，子表应如何响应。常见的级联操作有：

* **CASCADE**：当父表的数据被删除或更新时，自动删除或更新子表中相关的数据。
* **SET NULL**：当父表的数据被删除或更新时，子表中的外键列将被设置为 NULL。
* **SET DEFAULT**：当父表的数据被删除或更新时，子表中的外键列将被设置为默认值。
* **RESTRICT**：当父表的数据被删除或更新时，如果子表中有相关数据，则操作将被拒绝。
* **NO ACTION**：当父表的数据被删除或更新时，不会对子表执行任何操作，实际上与 RESTRICT 类似，但是在检查约束时延迟执行。

### 创建带有级联操作的外键约束

可以在创建外键时指定级联操作。例如，以下定义了当 `customers` 表中的记录被删除时，自动删除 `orders` 表中的相关记录：

```sql
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT,
  amount DECIMAL,
  FOREIGN KEY (customer_id) REFERENCES customers(id)
  ON DELETE CASCADE
);
```

在这个例子中，`ON DELETE CASCADE` 表示当 `customers` 表中的记录被删除时，`orders` 表中所有关联的记录也会被删除。

### 级联操作的示例

#### CASCADE

```sql
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT,
  amount DECIMAL,
  FOREIGN KEY (customer_id) REFERENCES customers(id)
  ON DELETE CASCADE
);
```

当删除 `customers` 表中的某个客户时，所有与该客户相关的订单记录也会被删除。

#### SET NULL

```sql
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT,
  amount DECIMAL,
  FOREIGN KEY (customer_id) REFERENCES customers(id)
  ON DELETE SET NULL
);
```

当删除 `customers` 表中的某个客户时，所有与该客户相关的订单记录中的 `customer_id` 会被设置为 `NULL`。

#### SET DEFAULT

```sql
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT DEFAULT 1,
  amount DECIMAL,
  FOREIGN KEY (customer_id) REFERENCES customers(id)
  ON DELETE SET DEFAULT
);
```

当删除 `customers` 表中的某个客户时，所有与该客户相关的订单记录中的 `customer_id` 会被设置为默认值 `1`。

#### RESTRICT

```sql
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT,
  amount DECIMAL,
  FOREIGN KEY (customer_id) REFERENCES customers(id)
  ON DELETE RESTRICT
);
```

当试图删除 `customers` 表中的某个客户时，如果该客户有相关的订单记录，删除操作会被拒绝。

#### NO ACTION

```sql
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT,
  amount DECIMAL,
  FOREIGN KEY (customer_id) REFERENCES customers(id)
  ON DELETE NO ACTION
);
```

与 `RESTRICT` 类似，但 `NO ACTION` 会在稍后的检查阶段进行延迟检查。

## 外键约束的优化

* **使用索引**：虽然 PostgreSQL 会为外键列自动创建索引，但如果外键列上没有索引，查询性能可能会下降。对于外键列，尤其是用于删除和更新的外键列，建议手动创建索引以提高性能。

```sql
CREATE INDEX idx_orders_customer_id ON orders (customer_id);
```

* **避免循环依赖**：在某些复杂的关系中，可能会产生循环外键依赖，导致插入和删除操作产生问题。要避免设计中出现循环依赖。

## 小结

外键约束和级联操作是关系型数据库中非常重要的数据完整性工具。外键用于确保表之间的数据一致性，而级联操作则可以自动处理父表数据变化对子表数据的影响。在设计数据库时合理使用外键和级联操作，可以减少数据不一致和维护复杂度。
