🗄️ 第四章 管理数据-避免非法数据的功能
📄 一句话概括

外键是表与表之间的挂钩:这一列的值必须是别表主键里真实存在的——不存在的班级编号插不进学生表,还带学生的班级也删不掉。它把「两张表对得上」从愿望变成铁律。

📝 本节和前面几节的关系

2-3 首次提出「外键=借来别表的主键」,3-21/3-22 用 JOIN 沿它连表——本节给它上约束这把锁。到本节为止,4-6 约束总图上的成员全部到齐;从 4-14 起,话题切换到另一路防线:事务。

一、没有外键约束的世界

-- 两张表:classes(1,2,3 班) 和 students
INSERT INTO students VALUES (1, '张三', 9);   -- 9 班?根本不存在!照样插入成功

脏数据就这么进来了:JOIN 一查,张三人间蒸发(在 classes 表里找不到 9 班)。外键约束就是堵这个洞。

二、加锁后的两条铁律

CREATE TABLE classes (
    id   INT PRIMARY KEY,
    name VARCHAR(20)
);

CREATE TABLE students (
    id         INT PRIMARY KEY,
    name       VARCHAR(20),
    class_id   INT,
    FOREIGN KEY (class_id) REFERENCES classes(id)   -- 挂钩
);
  1. 插入/更新被管:students 的 class_id 只能填 classes 里真实存在的 id(填 9 → 报错)
  2. 删除/更新反向被管:classes 里还挂着学生的班删不掉(先处理学生,或定义级联规则)

三、级联:删除时的自动化选择

FOREIGN KEY (class_id) REFERENCES classes(id)
    ON DELETE CASCADE      -- 班级删,学生跟着删
    -- 或 ON DELETE SET NULL / 默认 RESTRICT(拦住不让删)
规则 删班级时学生怎么办
RESTRICT(默认) 不许删班,先疏散学生
CASCADE 学生一起删(慎用!)
SET NULL 学生 class_id 置空

提示:SQLite 命令行默认不启用外键检查,要先执行 PRAGMA foreign_keys = ON; 才生效——Demo 里别忘了。

四、动手 Demo:插不进与删不掉

PRAGMA foreign_keys = ON;   -- SQLite 必须先开(MySQL 自带开启)

CREATE TABLE classes (id INT PRIMARY KEY, name VARCHAR(20));
CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(20),
    class_id INT,
    FOREIGN KEY (class_id) REFERENCES classes(id)
);

INSERT INTO classes VALUES (1, '一班');
INSERT INTO students VALUES (1, '张三', 1);    -- 1 班存在,成功
INSERT INTO students VALUES (2, '李四', 9);    -- 9 班不存在 → 报错
DELETE FROM classes WHERE id = 1;              -- 班里还有张三 → 报错
DELETE FROM students WHERE id = 1;             -- 先疏散学生
DELETE FROM classes WHERE id = 1;              -- 现在删得掉了

五、30 秒实验

把 PRAGMA foreign_keys = ON; 注释掉重跑——两条报错全部变成「成功」,脏数据畅通无阻。约束要先打开才会执法,这是 SQLite 新手最常踩的静默坑。

六、常见误区

自测三问 1. 外键约束管住哪两个方向?(插入端只许填存在的值;删除端不许留下孤儿) 2. 三种级联规则分别是什么?(RESTRICT 拦住 / CASCADE 连坐删除 / SET NULL 置空) 3. SQLite 里外键不生效先干什么?(PRAGMA foreign_keys = ON)

相关笔记