mysql的check约束仅在8.0.16+真正生效,低版本仅语法解析不校验;需实测插入非法值验证,且依赖严格模式、innodb引擎和dynamic行格式。

MySQL 的 CHECK 约束不是“写了就生效”,它只在 MySQL 8.0.16 及更高版本中真正起作用;低于该版本(包括 8.0.0–8.0.15)定义了也白定义,INSERT 违反约束照样成功。
确认 CHECK 是否真生效:别信版本号,要实测
只看 SELECT @@version 返回 8.0.x 不够——必须是 8.0.16 或更新(如 8.0.33、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.,说明生效;否则就是“假支持”。
- 不要依赖 Docker 镜像标签(如
mysql:8.0),拉取后务必执行SELECT VERSION() - 云数据库控制台显示“MySQL 8.0”不等于底层真实版本达标
-
SHOW CREATE TABLE输出里没出现CHECK子句,说明语法被忽略(引擎不支持或模式不兼容)
列级 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) );
- 表级
CHECK是实现“发货时间不能早于下单时间”这类业务逻辑的唯一原生方式 - 表达式中含
NULL时结果为UNKNOWN,约束不触发(这是 SQL 标准行为,不是 bug) - 列级和表级可共存,但约束名不能重复(同一 schema 内所有约束共享命名空间)
ALTER TABLE 添加 CHECK 的常见失败原因
对已有表执行 ALTER TABLE t ADD CHECK (email LIKE '%@%') 很容易失败,根本原因不是语法错,而是现有数据不满足新约束。
错误信息通常是:ERROR 3819 (HY000): Check constraint 't_chk_1' is violated.
- 必须先清洗历史数据:比如用
UPDATE t SET email = 'unknown@example.com' WHERE email NOT LIKE '%@%'或删掉非法行 - 若无法清洗,可用
NOT ENFORCED先注册约束,后续再激活:ADD CONSTRAINT chk_email_valid CHECK (email LIKE '%@%') NOT ENFORCED - MySQL 不支持直接修改已存在约束的
ENFORCED状态,想启用必须先DROP CHECK再ADD(需提前记下约束名) - 约束名强烈建议显式指定(如
chk_email_format),否则自动生成的t_chk_1类名字极难定位和维护
CHECK 表达式里哪些东西绝对不能写
MySQL 8.0 明确禁止在 CHECK 中使用非确定性表达式,否则建表或 ALTER 直接报错。
以下写法全部非法:
CHECK (NOW() > '2020-01-01') -- ❌ ERROR 3816: Constant, random or timezone-dependent expressions CHECK (UUID() IS NOT NULL) -- ❌ 同上 CHECK (score > (SELECT AVG(score) FROM t2)) -- ❌ 子查询不被允许 CHECK (@user_role = 'admin') -- ❌ 用户变量不被允许
-
CURRENT_DATE()、UNIX_TIMESTAMP()、RAND()全部禁用 - 允许的只有列名、常量、确定性函数(如
UPPER()、LENGTH()、正则REGEXP) - 枚举校验推荐用
CHECK (status IN ('pending', 'shipped')),比ENUM更易扩展 -
TINYINT UNSIGNED本身已限制 ≥0,再加CHECK (age BETWEEN 0 AND 150)是冗余但安全的显式表达
最容易被忽略的是:CHECK 生效需要整个上下文配合——严格 SQL 模式(STRICT_TRANS_TABLES)、InnoDB 引擎、ROW_FORMAT=DYNAMIC,三者缺一不可。哪怕版本达标,只要其中一项不满足,约束依然形同虚设。











