CHECK约束在MySQL 8.0中需同时满足版本≥8.0.16、strict模式启用、InnoDB引擎、ROW_FORMAT=DYNAMIC四条件才真正生效;实测INSERT非法值报错且SHOW CREATE TABLE显示CHECK子句,才是真支持。

CHECK约束在MySQL 8.0中不是“写了就生效”,必须满足版本、模式、引擎、格式四重条件,否则插入非法值照样成功。
确认 CHECK 是否真生效:别信版本号,要实测报错
SELECT @@version 返回 8.0.33 是必要但不充分条件——必须是 8.0.16 或更高(如 8.0.40、8.4.0)。很多生产环境标称“MySQL 8.0”,实际是未升级的 8.0.14 或打补丁失败的 8.0.28,CHECK 仍被静默忽略。
最可靠验证方式只有一步:
执行 CREATE TABLE t (x INT, CONSTRAINT chk_x CHECK (x > 0));,再执行 INSERT INTO t VALUES (-1);。
若报错 Check constraint 'chk_x' is violated.,说明真正生效;否则就是“假支持”。
额外检查项:
- 运行
SHOW CREATE TABLE t;,输出中必须明确出现CHECK (x > 0)字样;若缺失,说明语法被跳过(常见于 MyISAM 引擎或非严格模式) - Docker 镜像标签
mysql:8.0不代表真实版本,拉取后务必执行SELECT VERSION() - 云数据库控制台显示“MySQL 8.0” ≠ 底层版本达标,需登录后查证
列级 CHECK vs 表级 CHECK:能写什么,取决于定义位置
列级 CHECK 必须写在列定义内部,只能引用当前列;表级 CHECK 写在所有列定义之后,可跨列组合判断——这是业务逻辑落地的关键分水岭。
列级写法(合法):
CREATE TABLE users (
age TINYINT UNSIGNED,
status VARCHAR(20) CHECK (status IN ('active','inactive'))
);列级写法(非法):
status VARCHAR(20) CHECK (status IN ('active','inactive') AND created_at IS NOT NULL) -- ❌ created_at 尚未声明表级写法(合法,且唯一能实现该逻辑):
CREATE TABLE orders ( order_time DATETIME, ship_time DATETIME, CONSTRAINT chk_ship_after_order CHECK (ship_time >= order_time OR ship_time IS NULL) );
注意:
– NULL 参与比较结果为 UNKNOWN,约束不触发(SQL 标准行为,不是 bug)
– 表达式中禁止使用 NOW()、CURRENT_DATE()、UUID() 等非确定性函数,否则报错 ERROR 3816
让 CHECK 生效的四个硬性前提
即使版本达标,CHECK 仍可能被绕过。必须同时满足:
-
sql_mode中包含STRICT_TRANS_TABLES(推荐设为STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION),否则非法值会被截断或转成默认值,CHECK 失效 - 存储引擎必须是
InnoDB(MyISAM完全不支持 CHECK) - 表格式必须是
ROW_FORMAT=DYNAMIC(REDUNDANT在 8.0+ 已弃用,且部分 CHECK 行为异常) - 历史数据必须全部满足新约束——对已有表执行
ALTER TABLE ADD CHECK (email LIKE '%@%')前,必须先清理或修正所有不合规的email值,否则报错ERROR 3819
建表时建议显式指定关键参数:
SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
age TINYINT UNSIGNED,
status ENUM('active','inactive'),
CHECK (age BETWEEN 0 AND 150),
CONSTRAINT chk_status_valid CHECK (status IN ('active','inactive'))
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;ALTER TABLE 添加 CHECK 的典型失败场景
给已有表加 CHECK 最容易卡在数据校验环节,不是语法问题,而是存量数据不合规。
例如执行:
ALTER TABLE users ADD CONSTRAINT chk_email_format CHECK (email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');会直接失败,除非你已确保:
- 所有现有
email字段值都匹配正则(包括 NULL 和空字符串是否允许) - 没有
email = ''或email = 'invalid'这类脏数据 - 表中无违反其他隐含约束的行(如字段类型不兼容)
安全做法是分两步:
先用 SELECT COUNT(*) FROM users WHERE email NOT REGEXP '...'; 扫描问题数据
再修复或归档后,再执行 ADD CHECK
另外,NOT ENFORCED 是灰度上线的实用开关,例如:ADD CONSTRAINT chk_price CHECK (price > 0) NOT ENFORCED,后续确认无误再 ALTER TABLE ... ALTER CHECK chk_price ENFORCED;
复杂校验(如调用函数、关联查询、跨表逻辑)MySQL 原生不支持,必须用触发器或应用层兜底——CHECK 不是万能胶,只是数据防线的第一道刻痕。


















