1. CHECK 约束在 MySQL 8.0 之前为何被静默忽略?8.0 之后如何处理跨行 CHECK(如 sum(balance) = 0)?
CHECK 约束在 MySQL 8.0 之前为何被静默忽略?8.0 之后如何处理跨行 CHECK(如 sum(balance) = 0)这类约束?
- MySQL 8.0 前 CHECK 被解析但忽略
- 8.0 后 CHECK 的启用与执行
- 跨行 CHECK 的触发器替代方案
MySQL 8.0 之前,CHECK 子句在语法上被接受但语义上被忽略:建表时解析并丢弃,不生成约束对象、不校验数据,用户以为有约束实际上没有,这是"静默忽略"的根源(为兼容 SQL 标准语法而保留解析,却不实现执行)。5.7 里唯一例外是 CHECK 中的某些表达式会影响派生列等极少数场景,整体仍视为无效。8.0.16 起 CHECK 约束真正生效:支持列级与表级 CHECK、强制校验、命名与删除,违反时报错并可通过 SHOW CREATE TABLE、INFORMATION_SCHEMA.CHECK_CONSTRAINTS 查看。
跨行 CHECK(如 CHECK (sum(balance) = 0))在 8.0 依然不支持:CHECK 只能引用当前行的列,无法引用其他行或做聚合,标准上也不允许(断言 ASSERTION 才支持跨行,但无实现)。替代方案:用触发器在 INSERT/UPDATE/DELETE 后校验(BEFORE/AFTER TRIGGER 中 SELECT SUM 并 SIGNAL SQLSTATE 抛错),或用应用层事务内校验、定时对账任务;PostgreSQL 同样用触发器实现跨表/跨行校验,因为它也没有断言。设计建议:跨行不变式应记录在文档与测试中,用触发器加约束双重防护。
答题按时间线讲清"8.0 前解析即弃、8.0.16 后真正生效",再说明跨行 CHECK 在标准与实现上都不支持,给出触发器与对账替代方案,覆盖版本差异与替代设计。
-- MySQL 8.0.16+:生效的 CHECK
CREATE TABLE account (
id INT PRIMARY KEY,
balance DECIMAL(12,2) CHECK (balance >= 0)
);
-- 跨行校验改用触发器
CREATE TRIGGER trg_check_total BEFORE INSERT ON ledger
FOR EACH ROW
BEGIN
IF (SELECT SUM(amount) FROM ledger) + NEW.amount <> 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'total must be zero';
END IF;
END;