mysql 8.0.16+ 支持标准 check 约束,用于字段级基础校验;需用 char_length() 替代 length() 避免字节与字符混淆,且表达式须为确定性函数。

如何用 CHECK 约束做字段级基础校验
MySQL 8.0.16+ 才真正支持标准 CHECK 约束(之前版本会解析但不生效),这是最直接的声明式校验方式。它在写入时由引擎层拦截,比应用层校验更可靠。
常见误用是写成带函数的表达式,比如 CHECK(LENGTH(phone) = 11) —— 这在 MySQL 中合法,但要注意:LENGTH() 计算的是字节数,不是字符数;中文或 emoji 可能导致意外失败。应改用 CHAR_LENGTH()。
CHECK(age >= 0 AND age 比应用层 if 判断更防绕过- 多个约束可共存,但命名必须唯一:
CONSTRAINT chk_email_format CHECK(email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') - ALTER TABLE 添加 CHECK 时,MySQL 会默认验证存量数据;若不满足,需先清理或加
NOT ENFORCED(仅 8.0.16+ 支持)
为什么不能只靠应用层校验
应用层校验容易被绕过:前端跳过 JS 校验、Postman 直发请求、SQL 注入后执行恶意 INSERT、甚至运维手动导入脏数据。数据库层校验是最后一道防线。
典型场景:用户注册接口校验了手机号格式,但后台脚本批量导入老数据时忘了复用同一套逻辑,结果写入了 '123' 这种无效值——而加了 CHECK(phone REGEXP '^1[3-9][0-9]{9}$') 后,该语句直接报错 Check constraint 'chk_phone' is violated。
- ORM 如 SQLAlchemy、MyBatis 不会自动同步 CHECK 规则,得人工维护两边一致性
- 备份恢复、主从同步、mysqldump 导入均受 CHECK 约束约束,保障全链路数据质量
- 注意:MySQL 的 CHECK 不支持子查询,也不能引用其他表字段
敏感字段脱敏与权限隔离实操
校验只是起点,安全实践要配合访问控制。比如身份证号、手机号这类字段,不应让所有开发都能 SELECT。
推荐做法是建视图 + 行级权限:
- 创建脱敏视图:
CREATE VIEW user_safe AS SELECT id, username, CONCAT(LEFT(id_card, 3), '****', RIGHT(id_card, 4)) AS id_card_masked FROM user; - 给开发账号授视图权限而非基表:
GRANT SELECT ON mydb.user_safe TO 'dev'@'%'; - 禁用基表 SELECT:
REVOKE SELECT ON mydb.user FROM 'dev'@'%';
注意:MySQL 8.0+ 支持角色(CREATE ROLE),可把权限打包分配,避免逐个用户授权出错;但角色不能跨实例继承,主从环境需分别配置。
备份与审计日志怎么配合校验
光有校验不够,得知道谁在什么时候写了什么。MySQL 自带的 general_log 性能损耗大,不建议长期开;更实用的是开启 binlog 并配合 mysqlbinlog 工具回溯。
- 确认 binlog 格式为
ROW(binlog_format = ROW)才能看到具体变更值,STATEMENT 格式只记 SQL 原文 - 定期用
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001抽样检查敏感字段是否被异常更新 - 对于关键表,可建触发器记录操作日志(但注意:触发器无法捕获 TRUNCATE 或 LOAD DATA)
真实项目里,CHECK 约束和 binlog 审计是两套独立机制:前者防误写,后者留证据。漏掉任何一环,都可能在出问题时既拦不住,又查不清。










