Note
约束
记录 MySQL 字段约束、外键约束及引用行为的语法和示例
约束
约束(Constraint)是由数据库强制执行的数据规则,用于阻止不符合要求的写入,保证数据的有效性、一致性和完整性。与只在应用代码中校验相比,约束会统一作用于所有写入数据库的入口。
约束不一定只作用于单个字段:它既可以定义在字段上,也可以作为表级规则约束一个或多个字段;外键约束还会维护表与表之间或同一张表内部的引用关系。
本文以 MySQL 8.0.16 及之后的版本、InnoDB 存储引擎和严格 SQL 模式为基准,介绍两类常用约束:
- 字段约束:限制字段值是否允许为空、是否重复、是否满足条件等。
- 外键约束:保证子表中的引用值能够在父表中找到对应记录。
字段约束
字段约束通常在创建表时声明,也可以在表创建后使用 ALTER TABLE 添加、修改或删除。
| 规则或属性 | 关键字 | 作用 |
|---|---|---|
| 非空约束 | NOT NULL |
禁止字段保存 SQL 的 NULL 值 |
| 唯一约束 | UNIQUE |
保证一个字段或字段组合中的非空值不重复 |
| 主键约束 | PRIMARY KEY |
唯一标识一行记录,主键值必须唯一且不能为 NULL |
| 默认值属性 | DEFAULT |
插入时省略字段或显式写 DEFAULT,使用预先设置的值 |
| 检查约束 | CHECK |
要求字段值满足指定的布尔表达式 |
| 自动增长属性 | AUTO_INCREMENT |
为整数索引字段自动生成递增编号 |
其中,DEFAULT 和 AUTO_INCREMENT 更准确地说是字段属性,而不是负责拒绝非法值的完整性约束。
非空约束
NULL 表示未知或缺失的值,不等于空字符串 ''、数字 0 或字符串 'NULL'。因此,NOT NULL 不会自动拒绝这些值;
字段名 数据类型 NOT NULL
唯一约束
UNIQUE 保证一个字段或字段组合中的非空值不重复。MySQL 使用唯一索引实现唯一约束。允许为 NULL 的唯一字段可以保存多个 NULL。
-- 单字段的字段级写法
字段名 数据类型 UNIQUE
-- 单字段或多字段的表级写法
CONSTRAINT 唯一约束名 UNIQUE (字段名1, 字段名2, ...)
主键约束
PRIMARY KEY 用于唯一标识表中的每一行记录,同时具有非空和唯一的性质。一张表只能有一个主键,但这个主键可以由一个或多个字段组成。
-- 单字段的字段级写法
字段名 数据类型 PRIMARY KEY
-- 单字段或联合主键的表级写法
PRIMARY KEY (字段名1, 字段名2, ...)
联合主键判断的是字段组合是否重复。例如,主键为 (student_id, course_id) 时,同一名学生可以选择多门课程,同一门课程也可以被多名学生选择,但同一个“学生与课程”组合不能重复。
默认值属性
DEFAULT 为字段设置默认值。默认值只在插入记录时省略该字段,或显式写入 DEFAULT 时使用。如果字段允许为 NULL,显式写入 NULL 保存的仍然是 NULL,不会自动替换为默认值。
字段名 数据类型 DEFAULT 默认值
检查约束
CHECK 要求每一行数据满足指定条件,可以限制数值范围、状态集合或多个字段之间的关系。
-- 字段级写法,只能引用当前字段
字段名 数据类型 CHECK (条件表达式)
-- 表级写法,可以引用一个或多个字段
CONSTRAINT 检查约束名 CHECK (条件表达式)
检查表达式的结果为 FALSE 时写入失败,为 TRUE 或 UNKNOWN 时可以通过。涉及 NULL 的比较通常得到 UNKNOWN,因此 CHECK (age BETWEEN 1 AND 120) 本身仍允许 age 为 NULL;如果年龄必须填写,还要添加 NOT NULL。
自动增长属性
AUTO_INCREMENT 为新增记录生成递增编号,通常和整数主键一起使用。每张表最多只能有一个自动增长字段,该字段必须建立索引,并且不能再声明普通的 DEFAULT 值。插入时省略该字段或写入 NULL,MySQL 会生成下一个编号;自动增长值只保证自动生成时不会与已有值冲突,不保证永久连续。插入失败、事务回滚、删除记录或显式写入较大编号,都可能让编号之间出现空缺。
字段名 整数类型 AUTO_INCREMENT
添加和删除字段约束
在已有表上添加或删除常用规则的基本语法如下:
-- 修改字段的非空和自动增长属性
ALTER TABLE 表名
MODIFY COLUMN 字段名 完整数据类型 [NULL | NOT NULL] [AUTO_INCREMENT];
-- 设置或删除默认值
ALTER TABLE 表名 ALTER COLUMN 字段名 SET DEFAULT 默认值;
ALTER TABLE 表名 ALTER COLUMN 字段名 DROP DEFAULT;
-- 添加表级约束
ALTER TABLE 表名 ADD PRIMARY KEY (字段列表);
ALTER TABLE 表名 ADD CONSTRAINT 唯一约束名 UNIQUE (字段列表);
ALTER TABLE 表名 ADD CONSTRAINT 检查约束名 CHECK (条件表达式);
-- 删除表级约束
ALTER TABLE 表名 DROP PRIMARY KEY;
ALTER TABLE 表名 DROP INDEX 唯一约束名;
ALTER TABLE 表名 DROP CHECK 检查约束名;
MODIFY COLUMN需要重新写出完整的字段定义。修改时如果遗漏原有的NOT NULL、DEFAULT、AUTO_INCREMENT等属性,这些属性可能被移除。
CREATE TABLE、ALTER TABLE 和 DROP TABLE 属于 DDL 语句,在 MySQL 中通常会隐式提交事务,不能依靠后续的 ROLLBACK 撤销。
外键约束
外键约束(Foreign Key Constraint)用于维护引用完整性。保存被引用值(引用值作主键)的表称为父表,保存外键的表称为子表;子表中的每个非空外键值,都必须在父表被引用字段中存在。
| 行为 | 作用 |
|---|---|
NO ACTION |
默认行为;存在匹配的子表记录时拒绝更新或删除父表记录 |
RESTRICT |
立即拒绝更新或删除父表记录;在 InnoDB 中与 NO ACTION 效果相同 |
CASCADE |
父表键更新时同步更新子表外键;父表记录删除时同步删除匹配的子表记录 |
SET NULL |
父表键更新或记录删除时,将匹配的子表外键设置为 NULL;外键字段必须允许为 NULL |
SET DEFAULT |
InnoDB 没有实现“把子表外键设为默认值”的可用行为,不应使用 |
当父表被引用键发生更新,或父表记录被删除时,ON UPDATE 和 ON DELETE 会通过“行为”决定如何处理匹配的子表记录。省略 ON UPDATE 或 ON DELETE 时,对应行为默认为 NO ACTION。InnoDB 不支持延迟检查,因此它与 RESTRICT 一样,会在语句执行时立即拒绝破坏引用完整性的操作。
创建外键
创建子表时可以直接声明外键:
CREATE TABLE 子表名 (
字段定义,
...,
[CONSTRAINT [外键名称]]
FOREIGN KEY (子表外键字段)
REFERENCES 父表名 (父表被引用字段)
[ON DELETE 引用行为]
[ON UPDATE 引用行为]
);
也可以为已有表添加外键:
ALTER TABLE 子表名
ADD [CONSTRAINT [外键名称]]
FOREIGN KEY (子表外键字段)
REFERENCES 父表名 (父表被引用字段)
[ON DELETE 引用行为]
[ON UPDATE 引用行为];
语法中的方括号表示可选部分,实际编写 SQL 时不输入方括号。建议显式命名外键,方便后续删除和排查问题。
外键定义应使用独立的表级 FOREIGN KEY 子句,不要只在字段定义后书写内联的 REFERENCES。
创建条件
创建 InnoDB 外键时需要注意:
- 引用其他表时,父表结构必须先存在,但父表可以暂时没有数据。插入或更新非空子表外键前,对应的父表记录必须存在;自引用外键则可以在当前表的建表语句中直接定义。
- 父表和子表应使用相同且支持外键的存储引擎,本文统一使用
InnoDB。 - 外键两端的字段类型必须兼容。整数类型的大小和是否为
UNSIGNED应一致;非二进制字符串的字符集和排序规则必须一致。 - 实际设计中应让外键引用父表的主键或
UNIQUE NOT NULL键,避免依赖非标准的非唯一键引用。 - 子表外键字段需要索引;
InnoDB会在缺少可用索引时自动创建。 - 为已有表添加外键时,现有的每个非空外键值都必须能够匹配父表记录,否则添加失败。
- 子表外键字段如果允许为
NULL,NULL不需要匹配父表中的记录;如果每条子表记录都必须属于某个父表记录,应再添加NOT NULL。
删除外键
ALTER TABLE 子表名
DROP FOREIGN KEY 外键名称;
DROP FOREIGN KEY 后面填写的是外键约束名。如果创建时没有显式命名,可以先查看建表语句:
SHOW CREATE TABLE 子表名;
删除外键约束后,InnoDB 为它创建的索引可能仍然保留。可以使用 SHOW INDEX FROM 子表名 检查,并在确定没有其他用途后单独删除该索引。
示例
数据准备
下面先创建用于演示字段约束的用户表,再创建 dept 部门表和 emp 员工表。准备阶段暂不为 emp.dept_id 添加外键,以便后续分别演示创建表时添加和使用 ALTER TABLE 添加外键。
以下代码会删除并重新创建同名演示表,请仅在练习数据库中执行。重复执行时必须先删除子表,再删除父表。
DROP TABLE IF EXISTS set_default_demo;
DROP TABLE IF EXISTS emp_create_fk_demo;
DROP TABLE IF EXISTS emp;
DROP TABLE IF EXISTS dept;
DROP TABLE IF EXISTS field_change_demo;
DROP TABLE IF EXISTS user_constraint_demo;
CREATE TABLE user_constraint_demo (
id INT UNSIGNED AUTO_INCREMENT COMMENT 'ID',
name VARCHAR(10) NOT NULL COMMENT '姓名',
age INT COMMENT '年龄',
status CHAR(1) DEFAULT '1' COMMENT '状态',
gender CHAR(1) COMMENT '性别',
PRIMARY KEY (id),
CONSTRAINT uk_user_constraint_name UNIQUE (name),
CONSTRAINT chk_user_constraint_age
CHECK (age > 0 AND age <= 120)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '字段约束演示表';
CREATE TABLE dept (
id INT AUTO_INCREMENT COMMENT 'ID',
name VARCHAR(50) NOT NULL COMMENT '部门名称',
PRIMARY KEY (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '部门表';
INSERT INTO dept (id, name)
VALUES
(1, '研发部'),
(2, '市场部'),
(3, '财务部'),
(4, '销售部'),
(5, '总经办');
CREATE TABLE emp (
id INT AUTO_INCREMENT COMMENT 'ID',
name VARCHAR(50) NOT NULL COMMENT '姓名',
age INT COMMENT '年龄',
job VARCHAR(20) COMMENT '职位',
salary INT COMMENT '薪资',
entrydate DATE COMMENT '入职时间',
managerid INT COMMENT '直属领导ID',
dept_id INT COMMENT '部门ID',
PRIMARY KEY (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '员工表';
INSERT INTO emp
(id, name, age, job, salary, entrydate, managerid, dept_id)
VALUES
(1, '金庸', 66, '总裁', 20000, '2000-01-01', NULL, 5),
(2, '张无忌', 20, '项目经理', 12500, '2005-12-05', 1, 1),
(3, '杨逍', 33, '开发', 8400, '2000-11-03', 2, 1),
(4, '韦一笑', 48, '开发', 11000, '2002-02-05', 2, 1),
(5, '常遇春', 43, '开发', 10500, '2004-09-07', 3, 1),
(6, '小昭', 19, '程序员鼓励师', 6600, '2004-10-12', 2, 1);
managerid 保存直属领导编号,但本节只围绕 emp.dept_id -> dept.id 展开,因此不再额外创建自引用外键。
标有“预期失败”的语句用于观察数据库报错,请逐条执行。部分客户端会在批量执行遇到第一个错误后停止,不再继续运行同一批次中的后续语句。
字段约束
合法数据与字段属性
下面三条记录分别演示省略默认值字段、显式使用 DEFAULT、覆盖默认值,以及年龄检查的上下边界:
INSERT INTO user_constraint_demo (name, age, gender)
VALUES ('张三', 20, '男');
INSERT INTO user_constraint_demo (name, age, status, gender)
VALUES ('李四', 120, DEFAULT, NULL);
INSERT INTO user_constraint_demo (name, age, status, gender)
VALUES ('王五', 1, '0', '女');
SELECT id, name, age, status, gender
FROM user_constraint_demo
ORDER BY id;
三条记录的 id 由 AUTO_INCREMENT 自动生成。张三省略了 status,李四显式写入 DEFAULT,两人的状态都会使用默认值 '1';王五显式写入 '0',因此不会使用默认值。gender 没有非空约束,所以李四的性别可以为 NULL。
NOT NULL
显式向 name 写入 NULL 会违反非空约束,下面的语句预期执行失败:
INSERT INTO user_constraint_demo (name, age)
VALUES (NULL, 20);
空字符串不是 NULL,因此下面的插入可以成功。事务回滚用于撤销演示数据:
START TRANSACTION;
INSERT INTO user_constraint_demo (name, age)
VALUES ('', 20);
SELECT id, name, age
FROM user_constraint_demo
WHERE name = '';
ROLLBACK;
UNIQUE
name 同时具有 NOT NULL 和 UNIQUE 约束。再次写入“张三”会违反唯一约束,下面的语句预期执行失败:
INSERT INTO user_constraint_demo (name, age)
VALUES ('张三', 30);
如果唯一字段允许为 NULL,MySQL 可以保存多个 NULL:
DROP TEMPORARY TABLE IF EXISTS unique_null_demo;
CREATE TEMPORARY TABLE unique_null_demo (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(10) UNIQUE
);
INSERT INTO unique_null_demo (code)
VALUES (NULL), (NULL), ('A001');
SELECT id, code
FROM unique_null_demo
ORDER BY id;
再次写入相同的非空值仍会失败:
-- 预期失败:A001 已经存在
INSERT INTO unique_null_demo (code)
VALUES ('A001');
DROP TEMPORARY TABLE unique_null_demo;
PRIMARY KEY
显式写入已经存在的主键值 1 会失败:
-- 预期失败:主键 1 已经存在
INSERT INTO user_constraint_demo (id, name, age)
VALUES (1, '赵六', 30);
因为 user_constraint_demo.id 同时具有 AUTO_INCREMENT 属性,向它写入 NULL 会生成新编号,而不是触发主键非空错误。下面使用联合主键单独演示主键组合的唯一和非空规则:
DROP TEMPORARY TABLE IF EXISTS course_selection_demo;
CREATE TEMPORARY TABLE course_selection_demo (
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id)
);
INSERT INTO course_selection_demo (student_id, course_id)
VALUES (1, 1), (1, 2), (2, 1);
SELECT student_id, course_id
FROM course_selection_demo
ORDER BY student_id, course_id;
再次写入相同的主键组合会失败:
-- 预期失败:(1, 1) 已经存在
INSERT INTO course_selection_demo (student_id, course_id)
VALUES (1, 1);
联合主键中的任意字段为 NULL 也会失败:
-- 预期失败:联合主键中的字段不能为 NULL
INSERT INTO course_selection_demo (student_id, course_id)
VALUES (NULL, 3);
DROP TEMPORARY TABLE course_selection_demo;
DEFAULT
前面的合法数据已经覆盖了省略 status、显式写 DEFAULT 和显式覆盖默认值三种情况。下面演示显式写入 NULL:
START TRANSACTION;
INSERT INTO user_constraint_demo (name, age, status)
VALUES ('赵六', 30, NULL);
SELECT name, status, status IS NULL AS is_null
FROM user_constraint_demo
WHERE name = '赵六';
ROLLBACK;
查询中的 is_null 返回 1,说明显式写入的 NULL 没有被默认值 '1' 替换。
CHECK
数据准备中的三条合法记录已经覆盖年龄 1 和 120 两个边界。年龄为 0 时低于下限,下面的语句预期执行失败:
-- 预期失败:年龄不能为 0
INSERT INTO user_constraint_demo (name, age)
VALUES ('年龄过小', 0);
年龄为 121 时高于上限,同样会失败:
-- 预期失败:年龄不能大于 120
INSERT INTO user_constraint_demo (name, age)
VALUES ('年龄过大', 121);
NULL 参与比较时,检查表达式得到 UNKNOWN,所以当前定义允许年龄未知:
START TRANSACTION;
INSERT INTO user_constraint_demo (name, age)
VALUES ('年龄未知', NULL);
SELECT name, age
FROM user_constraint_demo
WHERE name = '年龄未知';
ROLLBACK;
如果年龄必须填写,应把字段定义改为 age INT NOT NULL,再与现有的 CHECK 约束组合使用。
AUTO_INCREMENT
省略自动增长字段或显式写入 NULL,都会生成新编号:
START TRANSACTION;
INSERT INTO user_constraint_demo (id, name, age)
VALUES (NULL, '吴八', 30);
SELECT LAST_INSERT_ID() AS generated_id;
ROLLBACK;
即使事务回滚,已经申请的自动增长值也不保证被重新使用,因此后续编号出现空缺是正常现象。
在已有表上增删约束
下面先创建一张没有约束的空表,再使用 ALTER TABLE 添加字段属性和表级约束:
CREATE TABLE field_change_demo (
id INT NOT NULL,
code VARCHAR(20),
score INT,
status CHAR(1)
) ENGINE = InnoDB;
ALTER TABLE field_change_demo
MODIFY COLUMN code VARCHAR(20) NOT NULL,
ALTER COLUMN status SET DEFAULT '1',
ADD PRIMARY KEY (id),
ADD CONSTRAINT uk_field_change_code UNIQUE (code),
ADD CONSTRAINT chk_field_change_score
CHECK (score BETWEEN 0 AND 100);
ALTER TABLE field_change_demo
MODIFY COLUMN id INT NOT NULL AUTO_INCREMENT;
SHOW CREATE TABLE field_change_demo;
删除主键前要先移除依赖它的 AUTO_INCREMENT 属性。以下代码依次恢复字段定义并删除约束:
ALTER TABLE field_change_demo
MODIFY COLUMN id INT NOT NULL;
ALTER TABLE field_change_demo
MODIFY COLUMN code VARCHAR(20) NULL,
ALTER COLUMN status DROP DEFAULT,
DROP CHECK chk_field_change_score,
DROP INDEX uk_field_change_code,
DROP PRIMARY KEY;
DROP TABLE field_change_demo;
外键约束
创建子表时添加外键
下面创建一张临时演示用的员工表,并在建表语句中直接添加外键:
CREATE TABLE emp_create_fk_demo (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
dept_id INT,
CONSTRAINT fk_emp_create_demo_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id)
ON DELETE SET NULL
ON UPDATE CASCADE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
SHOW CREATE TABLE emp_create_fk_demo;
DROP TABLE emp_create_fk_demo;
dept_id 允许为 NULL,因此可以配合 ON DELETE SET NULL 使用。
为已有表添加外键
数据准备中的所有 emp.dept_id 都能在 dept.id 中找到对应值,因此可以直接添加外键:
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id);
SHOW CREATE TABLE emp;
这里省略了 ON DELETE 和 ON UPDATE,两者默认都是 NO ACTION。
验证子表引用
写入存在的部门编号可以成功;写入 NULL 也可以成功,因为 emp.dept_id 没有 NOT NULL 约束:
START TRANSACTION;
INSERT INTO emp (name, dept_id)
VALUES ('市场部测试员工', 2), ('待分配员工', NULL);
SELECT id, name, dept_id
FROM emp
WHERE name IN ('市场部测试员工', '待分配员工')
ORDER BY id;
ROLLBACK;
写入不存在的部门编号 99 会破坏引用完整性,下面的语句预期执行失败:
INSERT INTO emp (name, dept_id)
VALUES ('无效部门员工', 99);
NO ACTION 与 RESTRICT
研发部被多名员工引用。在默认的 NO ACTION 行为下,删除研发部会被立即拒绝:
-- 预期失败:研发部仍被员工引用
DELETE FROM dept
WHERE id = 1;
直接修改被引用的部门编号也会被拒绝:
-- 预期失败:被引用的部门编号不能直接修改
UPDATE dept
SET id = 10
WHERE id = 1;
把外键显式定义为 RESTRICT 后,执行这两条语句会得到相同结果。
市场部没有被准备数据中的员工引用,因此删除它不会破坏引用完整性:
START TRANSACTION;
DELETE FROM dept
WHERE id = 2;
SELECT id, name
FROM dept
ORDER BY id;
ROLLBACK;
删除外键与校验已有数据
删除外键后,数据库不再检查 emp.dept_id 与 dept.id 的关系:
ALTER TABLE emp
DROP FOREIGN KEY fk_emp_dept;
SHOW INDEX FROM emp;
INSERT INTO emp (name, dept_id)
VALUES ('无效部门员工', 99);
此时表中已经存在无效部门编号,直接重新添加外键会校验失败:
-- 预期失败:现有的 dept_id = 99 无法匹配父表
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id);
先清理无效数据,再重新添加约束:
DELETE FROM emp
WHERE name = '无效部门员工' AND dept_id = 99;
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT;
CASCADE
先把外键改为级联行为:
ALTER TABLE emp
DROP FOREIGN KEY fk_emp_dept;
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id)
ON DELETE CASCADE
ON UPDATE CASCADE;
下面在事务中依次演示更新级联和删除级联,最后使用 ROLLBACK 恢复行数据和引用值:
START TRANSACTION;
-- 父表编号由 1 改为 10,匹配的 emp.dept_id 会同步改为 10
UPDATE dept
SET id = 10
WHERE id = 1;
SELECT id, name, dept_id
FROM emp
WHERE dept_id = 10
ORDER BY id;
-- 删除部门 10,dept_id = 10 的员工记录也会被删除
DELETE FROM dept
WHERE id = 10;
SELECT id, name, dept_id
FROM emp
ORDER BY id;
ROLLBACK;
ROLLBACK 不保证自动增长计数器回退,因此后续插入记录的编号可能仍然出现空缺;如果需要完全重置演示环境,可以重新执行数据准备。
SET NULL
先把外键改为 SET NULL,前提是 emp.dept_id 允许保存 NULL:
ALTER TABLE emp
DROP FOREIGN KEY fk_emp_dept;
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id)
ON DELETE SET NULL
ON UPDATE SET NULL;
更新父表被引用键时,匹配的子表外键会变为 NULL;删除父表记录时也是如此:
START TRANSACTION;
-- 原来属于研发部的员工,其 dept_id 会被设置为 NULL
UPDATE dept
SET id = 10
WHERE id = 1;
SELECT id, name, dept_id
FROM emp
WHERE id BETWEEN 2 AND 6
ORDER BY id;
-- 金庸原来属于总经办,删除总经办后其 dept_id 会被设置为 NULL
DELETE FROM dept
WHERE id = 5;
SELECT id, name, dept_id
FROM emp
WHERE id = 1;
ROLLBACK;
SET DEFAULT
SET DEFAULT 不能作为 InnoDB 的成功案例。下面使用独立演示表展示其语法,避免影响 emp 当前的外键:
CREATE TABLE set_default_demo (
id INT PRIMARY KEY,
dept_id INT DEFAULT 2,
CONSTRAINT fk_set_default_demo_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id)
ON DELETE SET DEFAULT
ON UPDATE SET DEFAULT
) ENGINE = InnoDB;
按照 MySQL 官方文档,InnoDB 应拒绝这个定义。部分 MySQL 8.0 版本可能允许建表并在 SHOW CREATE TABLE 中保留相关子句,但父表发生变化时仍不会执行 SET DEFAULT 语义,因此同样不能使用。
如果当前版本接受了建表语句,可以删除这张独立演示表:
DROP TABLE IF EXISTS set_default_demo;
演示结束后,把 emp 的外键从 SET NULL 恢复为默认行为:
ALTER TABLE emp
DROP FOREIGN KEY fk_emp_dept;
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id)
REFERENCES dept (id);