建三张外键配置不同的子表,对同一个父行删除操作,验证CASCADE、SET NULL、默认RESTRICT三种约束给出的三种不同结局。
一张父表,三张子表分别配置不同的ON DELETE行为。
建表CREATE TABLE parent1 (id INT PRIMARY KEY); CREATE TABLE child1 (id INT PRIMARY KEY, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent1(id) ON DELETE CASCADE); CREATE TABLE child2 (id INT PRIMARY KEY, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent1(id) ON DELETE SET NULL); CREATE TABLE child3 (id INT PRIMARY KEY, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent1(id)); -- 默认RESTRICT 插入parent1(1),(2);child1/child2各插入3行(2行指向id=1,1行指向id=2)
执行DELETE FROM parent1 WHERE id=1。
结果DELETE FROM parent1 WHERE id=1; SELECT * FROM child1; → 只剩:(3, 2) ← id=1,2的两行(parent_id=1)被自动删除了
执行的只是对parent1一张表的DELETE,child1里两行parent_id=1的记录被InnoDB自动一起删除了——CASCADE的意思就是"父行没了,子行也没有存在的意义,一起删"。
同样的父行删除,child2是另一种结局。
结果SELECT * FROM child2; → (1, NULL) ← 行还在,parent_id变成NULL → (2, NULL) ← 同样行还在,parent_id变成NULL → (3, 2) ← 没受影响,因为它指向的是parent_id=2
child2里原本parent_id=1的两行,没有被删除,而是它们的 parent_id 列被自动改成了NULL——数据本身保留下来,只是"断开了跟已删除父行的关联"。
没有指定ON DELETE行为,默认是最保守的策略。
结果INSERT INTO child3 VALUES (1,2); DELETE FROM parent1 WHERE id=2; 报错: Cannot delete or update a parent row: a foreign key constraint fails (`demo`.`child3`, CONSTRAINT `child3_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent1` (`id`))
CASCADE 适合"子数据完全依附于父数据、没有父数据就没有存在意义"的场景(比如订单和订单明细);SET NULL 适合"子数据本身依然有价值,只是失去了这个关联"的场景(比如员工离职后,他经手的历史记录不该被删,但可以不再关联到已注销的员工id);默认的 RESTRICT 则是最保守的选择——只要还有东西引用着这个父行,就不允许删,逼着调用方先处理掉所有子数据,避免"手滑删了一个不该删的父行"引发连锁的隐性数据丢失。
每一步都是本文上面用MySQL跑出来的原始结果。
ON DELETE CASCADE / SET NULL / RESTRICT:对比父行删除时的三种行为