mysql 8.0.16+ 的 check 约束默认强制生效,需满足版本≥8.0.16且未声明 not enforced;对 null 返回 unknown 时不触发约束,故 not null 与 check 需分开定义。

MySQL 8.0.16+ 的 CHECK 约束是真生效的,但必须显式启用
从 MySQL 8.0.16 开始,CHECK 约束才真正具备强制校验能力。此前版本(如 5.7)虽然语法能通过,但插入 age = 16 这类违反条件的数据时完全不报错——它只是“挂名”,不执行。而 8.0.16+ 默认创建的 CHECK 是 ENFORCED 的,只要表达式求值为 FALSE(不是 NULL 或 TRUE),就会直接拒绝写入。
关键点在于:你不能只靠语法存在就认为它在起作用。必须确认两点:
- MySQL 版本 ≥ 8.0.16(用
SELECT @@version;验证) - 约束未被显式声明为
NOT ENFORCED(例如CHECK (age >= 18) NOT ENFORCED就等于没加)
如果你在 8.0.25 上建表后仍发现违规数据能插进去,大概率是建表语句里写了 NOT ENFORCED,或者用了低版本兼容模式迁移的旧表。
CHECK 表达式里 NULL 的行为容易被误读
CHECK 约束对 NULL 值是“宽容”的:只要表达式结果是 UNKNOWN(比如 age > 18 在 age 为 NULL 时),就不算违反约束。也就是说,NOT NULL 和 CHECK 必须分开写,不能指望 CHECK (age IS NOT NULL AND age >= 18) 替代非空约束——因为如果该列允许 NULL,那整条记录依然能插进去(此时 age IS NOT NULL 为 FALSE,整个 AND 表达式就是 FALSE,触发约束失败;但如果列本身定义为允许 NULL,而你又没写这个 CHECK,那就毫无限制)。
常见错误场景:
- 给一个允许
NULL的status列加CHECK (status IN ('active', 'inactive')),结果INSERT INTO t VALUES (NULL)成功——因为NULL IN (...)返回UNKNOWN,不违反约束 - 想用
CHECK (email LIKE '%@%')检查邮箱格式,但email列是NULLABLE,导致空值绕过校验
正确做法是:先用 NOT NULL 控制是否允许空,再用 CHECK 控制非空时的取值范围。
复合条件和跨列 CHECK 在实际建表中很实用,但有语法陷阱
MySQL 支持表级 CHECK,可以引用多列,比如订单金额不能为负、且退款金额不能超过原订单金额:
CREATE TABLE orders ( id BIGINT PRIMARY KEY, amount DECIMAL(10,2), refund_amount DECIMAL(10,2), CHECK (amount >= 0 AND refund_amount >= 0 AND refund_amount <p>注意几个易错点:</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2334" title="MySQL"><img src="https://img.php.cn/upload/skill/000/000/081/178900927846657.jpg" alt="MySQL" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="overflowclass">MySQL</a> <p class="overflowclass">编写正确的MySQL查询,避免字符集、索引和锁方面的常见陷阱。</p> </div> <a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 列名必须拼写准确,且不能引用尚未定义的列(除非用表级约束,它支持前向引用)
- 比较浮点/DECIMAL 时慎用
==,应改用范围或ABS(a - b) 类写法,避免精度问题导致意外拒绝 - 函数调用受限:不能在
CHECK中用NOW()、UUID()、用户变量等非确定性函数,否则建表会报错ERROR 3815
另外,MySQL 不支持在 CHECK 中调用自定义函数(UDF),哪怕它是 deterministic 的,这点和 PostgreSQL 不同。
线上环境加 CHECK 约束要小心历史数据冲突
用 ALTER TABLE ... ADD CONSTRAINT 给已有表加 CHECK 时,MySQL 会**立即验证全表数据**。如果存在任何一行让表达式返回 FALSE,整个语句就会失败,连约束都加不上。
比如你有一张百万行的 users 表,其中几条记录的 age 是 -1 或 200,此时执行:
ALTER TABLE users ADD CONSTRAINT chk_age CHECK (age BETWEEN 0 AND 150);
会直接报错:ERROR 4025 (23000): Check constraint 'chk_age' is violated.
解决路径只有两条:
- 先清理或修正脏数据(
UPDATE users SET age = NULL WHERE age NOT BETWEEN 0 AND 150;) - 或临时用
NOT ENFORCED加上约束,后续再择机启用(ALTER TABLE users ALTER CHECK chk_age ENFORCED;)
后者风险高:它会让约束“形同虚设”一段时间,团队可能误以为数据已受保护。所以更推荐把数据清洗作为前置动作,而不是绕过校验。
真正麻烦的不是语法,而是你加约束那一刻,数据库会把你过去所有放过的数据问题一次性翻出来打脸。别低估存量数据的“破坏力”。










