MySQL 8.0.16起CHECK约束才真正生效,此前版本仅语法支持但不执行校验;8.0.16+默认ENFORCED,支持表级和列级约束,用于强制数值范围、非空、逻辑一致性等静态表达式校验。

MySQL 8.0 中,绝大多数字段级校验应直接用 CHECK 约束,而不是触发器——它更轻、更透明、自带索引优化,且不会干扰事务流程。
为什么 CHECK 约束在 MySQL 8.0.16+ 才真正生效
MySQL 8.0.16 之前,CHECK (expr) 语法虽被接受,但完全不执行校验;升级后才真正强制执行。这意味着:
- 如果你用的是 8.0.15 或更早版本,
CHECK只是摆设,写上去也没用 - 8.0.16+ 默认启用
ENFORCED,除非显式声明NOT ENFORCED -
expr必须返回布尔值:TRUE或UNKNOWN(如含NULL运算)允许插入,FALSE则报错Check constraint 'xxx' is violated.
CHECK 能覆盖哪些典型校验场景
只要逻辑能用单条 SQL 表达式静态描述,就该优先用 CHECK:
- 数值范围:
age CHECK (age >= 0 AND age - 枚举限制:
status CHECK (status IN ('active', 'inactive', 'pending')) - 非空组合判断:
email CHECK (email IS NOT NULL OR phone IS NOT NULL) - 跨列逻辑:
discount_price CHECK (discount_price (注意:列名顺序无关,表级约束可引用任意列) - JSON 字段结构检查:
metadata CHECK (JSON_VALID(metadata) AND JSON_TYPE(metadata) = 'OBJECT')
触发器不是 CHECK 的“加强版”,而是兜底方案
当出现以下情况时,CHECK 无能为力,才考虑触发器:
- 需要查另一张表(如插入订单前查客户余额:
SELECT credit_limit FROM customers WHERE id = NEW.customer_id) - 依赖当前用户或会话变量(如
USER()、@current_tenant_id) - 需写日志表并带上下文(操作人、IP、时间戳等无法从
NEW/OLD直接获取的字段) - 多表级联清理(外键
ON DELETE CASCADE不支持跨三层以上关联)
此时务必注意:BEFORE INSERT 或 BEFORE UPDATE 触发器中若需查表,对应字段必须有索引;否则单次插入可能从几毫秒升到数百毫秒。别让 CHECK 能干的活,硬塞进触发器里扛着。
容易被忽略的关键细节
很多人写了 CHECK 却没生效,问题常出在:
- MySQL 版本低于 8.0.16 —— 先查
SELECT VERSION(); - 建表语句里漏了
ENGINE=InnoDB(MyISAM 完全不支持CHECK) - 表达式里用了函数但未声明为
DETERMINISTIC(如自定义函数),导致解析失败 - 约束名重复或超长(64 字符上限),MySQL 自动生成名如
sc_check_1,但手动命名更利于排查 - 把校验逻辑写在
AFTER触发器里,而错误已发生——CHECK是在数据写入前拦截,这才是真正兜底的位置


















