mysql 8.0.16+ 才真正支持 check 约束,此前版本仅解析但静默忽略;check 表达式须确定性,禁止子查询、函数及非确定性内容。

MySQL 8.0.16+ 才真正支持 CHECK 约束
低于这个版本(比如 5.7 或早期 8.0)即使写 CHECK 语法也不会生效——MySQL 会解析但静默忽略。执行 SHOW CREATE TABLE t; 查看建表语句,如果发现 CHECK 子句被丢掉了,基本就是版本不支持。
确认版本:运行 SELECT VERSION();。只有 8.0.16 及以上才启用该功能,默认开启,无需额外配置。
常见错误现象:INSERT 明明违反了 CHECK 条件却成功插入;或者建表时没报错,但校验形同虚设。
CHECK 表达式里不能用子查询、函数或非确定性内容
CHECK 要求表达式必须是「确定性」的,即每次对同一行计算结果一致,且不依赖外部状态。所以以下写法全都不合法:
CHECK (created_at —— <code>NOW()是非确定性函数-
CHECK (status IN (SELECT value FROM valid_status))—— 含子查询 -
CHECK (JSON_CONTAINS(metadata, '"active"'))—— MySQL 8.0.16–8.0.18 中部分 JSON 函数不被允许(具体看补丁版本)
能安全用的包括:>、、<code>IN(字面量列表)、IS NULL、CASE(简单分支)、字符串比较、数值运算等。
字段级 CHECK 和表级 CHECK 的行为差异
字段级写在列定义后:age INT CHECK (age >= 0 AND age ;表级写在最后:<code>CONSTRAINT chk_age CHECK (age >= 0)。两者都能生效,但注意:
- 字段级约束名默认由 MySQL 自动生成(如
chk_1),不易追踪;建议显式命名以便后续ALTER TABLE ... DROP CHECK - 表级约束可跨字段校验,例如:
CHECK (end_date IS NULL OR end_date >= start_date)—— 这是字段级做不到的 - 同一个字段参与多个 CHECK 时,任一失败即拒绝插入/更新,无短路逻辑
UPDATE 时 CHECK 会重新触发,但不会自动修复旧数据
加 CHECK 约束不影响已有数据——MySQL 不校验历史行。这意味着,如果你给已有表加约束,得先清理脏数据,否则 ALTER TABLE ... ADD CHECK 会直接报错:ERROR 3819 (HY000): Check constraint 'xxx' is violated。
实操建议:
- 先用
SELECT * FROM t WHERE NOT (your_condition);找出违规行 - 修复或删除后再加约束
- 加完之后,所有
INSERT/UPDATE都会实时校验,包括触发器生成的值(只要不是通过SET @var绕过)
一个容易被忽略的点:CHECK 不阻止 NULL,除非你显式写成 NOT NULL 或 CHECK (col IS NOT NULL) —— 很多人以为 CHECK (col > 0) 就能挡住 NULL,其实不会。











