MySQL 表约束详解:让非法数据无法进入数据库

MySQL 表约束详解:让非法数据无法进入数据库
Lichermione约束把业务规则写进表结构,使数据库成为数据完整性的最后防线。应用校验能改善用户体验,但无法代替 NOT NULL、主键、唯一键、外键和 CHECK 对所有写入入口的统一保护。
1. NOT NULL
1 | CREATE TABLE user_profile ( |
NOT NULL 表示列必须有值。它不禁止空字符串,因此若空字符串也非法,需要应用校验或 CHECK:
1 | nickname VARCHAR(100) NOT NULL, |
2. 默认值
1 | status TINYINT UNSIGNED NOT NULL DEFAULT 1, |
默认值只在插入时省略该列或显式使用 DEFAULT 时生效。显式插入 NULL 是否允许,仍由空属性决定。
不要用一个虚假默认值掩盖“业务上其实不知道”。例如未知生日应该是 NULL,而不是 1970-01-01。
3. 列注释
1 | status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1=正常, 2=禁用' |
COMMENT 是元数据,不会自动验证值是否合法。枚举语义仍应通过 CHECK、引用表或明确的类型约束实现。
4. ZEROFILL 为什么不适合业务格式
旧资料常演示 INT ZEROFILL,它只是显示层补零,并不改变存储值;现代 MySQL 中整数显示宽度与 ZEROFILL 已不应作为新设计依赖。
订单号、邮编、手机号等不是用于算术的值,应使用字符串:
1 | postal_code VARCHAR(20) NOT NULL |
格式化应由查询表达式或应用展示层完成。
5. 主键
主键要求唯一且非空,一张表只能有一个主键,但主键可以由多列组成:
1 | CREATE TABLE order_item ( |
InnoDB 将主键作为聚簇索引键,二级索引叶子记录也会保存主键值,因此主键宜稳定、短小,避免频繁更新。
6. AUTO_INCREMENT
1 | id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY |
自增保证数据库生成新的序号,但不保证:
- 连续无缺口;
- 按提交顺序严格递增;
- 删除后自动复用;
- 能代表业务含义。
事务回滚、并发插入和失败语句都可能留下缺口。自增 ID 适合作为代理主键,不适合当作精确计数器。
7. 唯一约束
1 | CONSTRAINT uk_users_email UNIQUE (email) |
唯一约束是并发环境中防止重复的可靠手段。应用层“先查再插”存在竞态条件,最终仍要处理数据库返回的重复键错误。
MySQL 唯一索引通常允许多个 NULL,因为 NULL 不被视为彼此相等。若业务要求最多一个空值,需要重新建模或使用其他约束策略。
8. 外键
1 | CREATE TABLE orders ( |
外键保证子表引用的父记录存在,并定义父记录变化时的行为:
RESTRICT/NO ACTION:存在引用时拒绝;CASCADE:级联更新或删除;SET NULL:把子表外键设为NULL,要求该列可空。
级联删除可能一次影响大量记录,使用前要确认业务含义和锁范围。是否使用外键可结合架构和运维能力决定,但“不建外键”不等于“不需要引用完整性”。
9. CHECK
1 | CREATE TABLE account ( |
MySQL 8.0.16 起会执行 CHECK 约束;旧版本可能只解析而不执行,因此迁移旧系统时必须确认服务器版本和历史数据。
约束表达式为 TRUE 或 UNKNOWN 时通过,为 FALSE 时失败,所以需要结合 NOT NULL 才能禁止空值绕过。
10. 综合建表示例
1 | CREATE TABLE product ( |
11. 添加与删除约束
1 | ALTER TABLE product |
已有数据违反新约束时,添加操作会失败。应先查询并清理异常数据,再上线约束。
12. 常见误区
- 认为
DEFAULT能替代NOT NULL; - 用
ZEROFILL保存业务编号; - 把自增值当作连续计数;
- 只在应用层检查唯一性;
- 误以为唯一索引不允许多个
NULL; - 使用
CHECK却没有确认旧版本是否执行; - 外键两端的类型、符号位或字符集不一致;
- 无评估地使用级联删除;
- 通过关闭外键检查导入数据,却没有在之后验证完整性。
约束应描述“数据永远必须满足什么”,而不是某个页面当前如何校验。规则一旦进入数据库,所有脚本、服务和人工操作都必须遵守。