约束与完整性
primary-key、foreign-key、unique、check、not-null。
约束与完整性
这一课你将能做到
- 在建表与改表时正确声明 PRIMARY KEY、UNIQUE、NOT NULL、DEFAULT,并解释唯一约束与唯一索引是同一件事
- 创建外键并实测 RESTRICT / CASCADE / SET NULL 三种 ON DELETE 行为的差异
- 用 CHECK 约束(8.0.16+ 生效)挡住非法数据,并说出大厂为什么经常不用物理外键
为什么要学
应用层的校验可以绕过——脚本直连数据库、另一个服务、运维手改数据,都绕过你的 Spring 校验注解。约束是数据库在存储层的最后防线:脏数据根本进不来,而不是进来了等着清洗。约束也是面试的雷区:「为什么你们不用外键?」「自增主键和 UUID 怎么选?」答不好,说明没在真实系统里做过数据一致性。
人话:约束就是数据库的「门禁系统」。NOT NULL 查有没有带工牌,UNIQUE 查有没有冒名顶替,外键查部门对不对得上,CHECK 查数值合不合法——全过才放行。
先建立图像
完整性的分层防线(从外到内越来越严格):
第 1 层 数据类型 runtime_ms 不可能存进 'abc'
第 2 层 NOT NULL 关键字段不许空
第 3 层 UNIQUE/PK 身份不许重复(uk_email、uk_username)
第 4 层 FOREIGN KEY 引用的对象必须真实存在
第 5 层 CHECK 值必须满足业务规则(runtime_ms >= 0)
第 6 层 应用层/触发器 复杂业务规则(余额不可为负等)
外键的三种 ON DELETE 行为:
父行(cf_user)被删除时,子行(cf_comment)怎么办?
RESTRICT → 拒绝删除(ERROR 1451) 「有人引用你,不许走」
CASCADE → 子行跟着删 「陪葬」
SET NULL → 子行的外键列置 NULL 「断开关系,各自安好」
跟着做
步骤 0:数据准备
USE sql_practice;
TRUNCATE TABLE cf_submission;
TRUNCATE TABLE cf_practice_record;
TRUNCATE TABLE cf_user;
TRUNCATE TABLE cf_problem;
-- 执行第 1 课步骤 4 的四段 INSERT,恢复初始数据
步骤 1:主键与唯一约束实战
-- 唯一键冲突(cf_problem.slug 已有 uk_slug)
INSERT INTO cf_problem (slug, title) VALUES ('two-sum', '重复题');
-- ERROR 1062 Duplicate entry 'two-sum' for key 'uk_slug'
-- NOT NULL 违反
INSERT INTO cf_user (username, email) VALUES (NULL, 'x@x.com');
-- ERROR 1048 Column 'username' cannot be null
再验证「唯一约束允许多个 NULL」:
CREATE TABLE t_tag (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) UNIQUE
) ENGINE=InnoDB;
INSERT INTO t_tag (name) VALUES ('dp'), (NULL), (NULL);
SELECT * FROM t_tag; -- 两个 NULL 都插入成功
DROP TABLE t_tag;
InnoDB 视 NULL 为「未知」,未知与未知不冲突,所以唯一索引列可以有多行 NULL——判断「是否已设置」要靠 IS NULL,而不是靠唯一约束。
步骤 2:外键之 RESTRICT——默认的「不许删」
CREATE TABLE cf_comment (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
content VARCHAR(500) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_comment_user
FOREIGN KEY (user_id) REFERENCES cf_user (id)
ON DELETE RESTRICT
) ENGINE=InnoDB;
INSERT INTO cf_comment (user_id, content) VALUES (1, '这道题解法很妙');
DELETE FROM cf_user WHERE id = 1;
-- ERROR 1451 Cannot delete or update a parent row:
-- a foreign key constraint fails (`sql_practice`.`cf_comment`, CONSTRAINT `fk_comment_user`)