mysql菜鸟教程

专栏导航

8.2 外键(FOREIGN KEY):表间关系

       在现实世界中,数据不是孤立存在的。比如,一个学生属于某个班级,一个订单属于某个客户,一篇文章属于某个作者。这种“属于”的关系在数据库中就是通过外键(FOREIGN KEY) 来实现的。外键是连接两张表的桥梁,它保证了数据之间的参照完整性

一、什么是外键?

外键 是一个表中的一列(或一组列),它的值必须引用另一个表中的主键唯一键。简单来说,外键告诉数据库:“这张表的这一列的值,必须在另一张表的对应列中存在”。

例如,在 students 表中,我们有一个 class_id 列,它引用 classes 表的 id 主键。这样,每个学生都被分配到一个真实存在的班级。

为什么需要外键?

  1. 保证数据一致性:防止插入无效的引用(比如给一个学生分配一个不存在的班级)。

  2. 防止孤立数据:当删除被引用的数据时,可以自动处理相关的子记录(例如,删除班级时,自动删除该班级的所有学生,或将其置空)。

  3. 清晰表达关系:数据库结构能够直观反映业务逻辑,便于理解和维护。

二、外键的创建语法

外键可以在创建表时定义,也可以使用 ALTER TABLE 后期添加。

1. 创建表时定义外键

CREATE TABLE 子表名 (
    列定义,
    ...
    [CONSTRAINT 外键名称] FOREIGN KEY (子表外键列) REFERENCES 父表名(父表主键列)
    [ON DELETE 行为]
    [ON UPDATE 行为]
);

示例:班级表和学生表

-- 先创建父表:班级表
CREATE TABLE classes (
    id INT PRIMARY KEY AUTO_INCREMENT,
    class_name VARCHAR(50) NOT NULL,
    grade INT
);

-- 创建子表:学生表,包含外键 class_id
CREATE TABLE students (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    age TINYINT,
    class_id INT,
    CONSTRAINT fk_student_class   -- 为外键起个名字(便于管理)
        FOREIGN KEY (class_id) 
        REFERENCES classes(id)
        ON DELETE SET NULL
        ON UPDATE CASCADE
);

2. 使用 ALTER TABLE 添加外键

如果表已经存在,可以这样添加外键(前提是已有数据满足外键约束):

ALTER TABLE students
ADD CONSTRAINT fk_student_class
FOREIGN KEY (class_id) REFERENCES classes(id)
ON DELETE SET NULL
ON UPDATE CASCADE;

3. 删除外键

ALTER TABLE students DROP FOREIGN KEY fk_student_class;

三、外键约束的行为:ON DELETE 和 ON UPDATE

当父表中的被引用行发生删除或更新时,我们可以指定子表该如何应对。MySQL 支持以下四种行为:

行为

含义

适用场景

CASCADE

级联操作。父表删除/更新时,子表中匹配的行也自动删除/更新。

删除订单时自动删除订单明细;更新客户ID时自动更新订单中的客户ID。

SET NULL

置空。父表删除/更新时,将子表中的外键列设为 NULL(要求该列允许 NULL)。

删除班级后,将该班学生的班级ID设为 NULL(保留学生记录)。

RESTRICT

限制。如果子表中存在引用,则拒绝父表的删除/更新操作。

默认行为(如果未指定)。防止删除仍有学生的班级。

NO ACTION

与 RESTRICT 类似,但在某些数据库中略有差异(MySQL 中同 RESTRICT)。

同上。

注意:如果使用 SET NULL,外键列必须允许 NULL 值,否则操作会失败。

四、实战案例:订单与客户

让我们通过一个更贴近业务的例子来理解外键的应用。

场景

  • 一个客户可以拥有多个订单(一对多关系)。

  • 订单必须属于一个已存在的客户。

  • 如果客户被删除,应该如何处理其订单?(这里我们选择:删除客户时,同时删除其所有订单)

建表

-- 客户表
CREATE TABLE customers (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20)
);

-- 订单表,包含外键 customer_id
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    amount DECIMAL(10,2) NOT NULL,
    customer_id INT NOT NULL,
    CONSTRAINT fk_order_customer
        FOREIGN KEY (customer_id) 
        REFERENCES customers(id)
        ON DELETE CASCADE
        ON UPDATE CASCADE
);

插入数据测试

-- 插入客户
INSERT INTO customers (name, email) VALUES
('张三', 'zhangsan@example.com'),
('李四', 'lisi@example.com');

-- 插入订单
INSERT INTO orders (amount, customer_id) VALUES
(299.00, 1),
(59.90, 1),
(1200.00, 2);

-- 尝试插入一个不存在的客户ID(应该失败)
INSERT INTO orders (amount, customer_id) VALUES (100.00, 99);
-- 错误:Cannot add or update a child row: a foreign key constraint fails

删除客户,观察级联效果

-- 删除客户“张三”(id=1)
DELETE FROM customers WHERE id = 1;

-- 查询 orders 表,发现张三的订单也被自动删除了
SELECT * FROM orders;  -- 只剩下 id=2 的订单(属于李四)

如果使用 SET NULL 会怎样?

假设我们将外键设为 ON DELETE SET NULL,并允许 customer_id 为 NULL:

-- 修改外键行为
ALTER TABLE orders DROP FOREIGN KEY fk_order_customer;
ALTER TABLE orders ADD CONSTRAINT fk_order_customer
    FOREIGN KEY (customer_id) REFERENCES customers(id)
    ON DELETE SET NULL;
-- 注意:需要先让 customer_id 允许 NULL
ALTER TABLE orders MODIFY customer_id INT NULL;

-- 再删除客户“李四”
DELETE FROM customers WHERE id = 2;

-- 查询 orders,发现原先属于李四的订单的 customer_id 变成了 NULL

五、外键使用注意事项

  1. 数据类型必须匹配:外键列与引用的主键列必须数据类型一致(长度、符号等也要一致)。

  2. 引用的列必须是主键或唯一键:通常引用主键,但也可以引用唯一键(UNIQUE)。

  3. 存储引擎限制:MySQL 中只有 InnoDB 存储引擎支持外键约束(MyISAM 不支持,但可以定义语法,实际不生效)。

  4. 性能影响:外键约束会带来额外的检查开销,但在保证数据完整性方面非常值得。对高并发写入频繁的表,可能需要权衡。

  5. 循环依赖:应避免两个表互相作为外键(鸡生蛋蛋生鸡问题),这会很难插入数据。

  6. 外键名:建议给外键起一个有意义的名字(如 fk_订单_客户),便于管理和错误排查。

六、如何查看外键关系?

可以使用以下 SQL 查看某个数据库中的所有外键:

SELECT 
    CONSTRAINT_NAME,
    TABLE_NAME,
    COLUMN_NAME,
    REFERENCED_TABLE_NAME,
    REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_SCHEMA = '数据库名'
  AND REFERENCED_TABLE_NAME IS NOT NULL;

七、外键的最佳实践

  1. 为每个表设计主键:这是建立外键关系的基础。

  2. 谨慎选择约束行为:根据业务逻辑选择 CASCADESET NULL 或 RESTRICT,避免意外删除大量数据。

  3. 命名规范:外键名建议以 fk_ 开头,后跟子表名和父表名,如 fk_orders_customers

  4. 备份与测试:在生产环境添加或修改外键前,务必在测试环境验证,并备份数据。

  5. 考虑性能:如果表非常大且写操作频繁,可考虑在应用层实现参照完整性,以换取更高性能。

八、综合练习

设计一个“博客系统”的数据库,包含三张表:

  • users(用户表):id(主键)、usernameemail

  • posts(文章表):id(主键)、titlecontentuser_id(外键引用 users.id,级联删除)

  • comments(评论表):id(主键)、contentpost_id(外键引用 posts.id,级联删除)、user_id(外键引用 users.id,设置 NULL)

要求:

  1. 写出建表语句,包含合适的外键约束。

  2. 插入几条测试数据。

  3. 尝试删除一个用户,观察其文章和评论的变化。

九、总结

  • 外键 是表与表之间的“法律条约”,它强制子表的引用必须在父表中存在。

  • 外键保证了数据的参照完整性,防止了“孤儿数据”的出现。

  • 通过 ON DELETE 和 ON UPDATE 可以定义父表变化时的自动行为。

  • 外键的设计需要结合实际业务,选择适当的约束策略。

  • 虽然外键会带来一些性能开销,但对于保证数据一致性来说,它是最简单可靠的方案。


发表评论

昵称:
联系方式:
评论内容:

所有评论

关于我 备案号:蜀ICP备2023042032号-1