约束用于保证数据完整性(实体完整性、参照完整性、域完整性、业务完整性),不是越多越好,约束会带来写入开销,不合理使用会造成锁等待、慢 DML、业务报错难以排查。下面分主键、唯一、外键、非空、check、默认值,给出选型、避坑、最佳实践。
1. 主键约束 PRIMARY KEY
实体完整性,一张表只能一个主键,隐含非空 + 唯一索引。
✅ 推荐做法
优先使用无业务含义代理主键(自增 ID、雪花 ID、UUIDv4),不要用业务字段做主键(手机号、身份证、订单编号)。业务字段会更新,更新主键会连带更新二级索引,代价极高。
InnoDB 主键尽量用整型有序主键,减少页分裂,提升写入性能。
主键必须非空,不要再额外加
NOT NULL,主键自带非空。避免复合主键,复合主键会让所有二级索引带上全部主键字段,索引膨胀。
❌ 不推荐
业务字段作为主键;大字符串做主键;超长复合主键。
2. 唯一约束 UNIQUE
保证字段 / 组合不重复,底层会创建唯一索引。
✅ 推荐
业务唯一性(手机号、邮箱)使用
UNIQUE约束,防止业务层漏校验产生脏数据。多字段唯一,使用组合唯一约束,而不是程序层判断。
注意:MySQL 中
UNIQUE允许多个 NULL,NULL 不等于 NULL;如果业务要求不能有空,需要搭配NOT NULL。
⚠️ 坑点
唯一索引在大量并发插入重复数据时,会产生锁等待、死锁,高并发场景不能完全依赖唯一约束做防重,业务层先做幂等。
不要滥用大量唯一约束,每个唯一约束对应一个索引,增大写开销。
高并发场景:业务幂等优先,约束做兜底,不要把约束当主逻辑。
3. 外键约束 FOREIGN KEY(参照完整性)
保证关联数据一致性,生产环境很多业务会禁用数据库外键,改为应用层维护。
✅ 适合使用外键场景
内部小系统、数据量不大、并发不高,希望数据库强制保证参照完整性,防止脏数据。
测试库、中台元数据表。
❌ 不建议开启外键场景(互联网高并发业务)
外键会触发级联锁,DML 操作父 / 子表会互相加锁,极易死锁、锁等待;
批量删除、批量更新性能差;
分库分表之后数据库外键完全失效;
迁移、数据同步、备份恢复容易受外键约束报错。
最佳实践:高并发、分表项目关闭数据库外键,在应用层实现参照校验;外键仅做低并发库强约束。
如果必须使用外键:谨慎使用
ON DELETE CASCADE级联删除,级联删除大数量会锁表,优先用ON DELETE SET NULL。
4. 非空约束 NOT NULL(域完整性)
✅ 推荐
业务上永远不会为空的字段,强制设置
NOT NULL,配合DEFAULT默认值。避免大量业务逻辑去判断
IS NULL,简化 SQL,同时索引效率更好。字符串类型不要用
NULL代表空业务含义,用空字符串''。
⚠️ 避坑
已有大表增加
NOT NULL约束,MySQL 5.7/8.0 部分版本会锁表;大表 DDL 用 Online DDL 工具。不要无脑全部加 NOT NULL;允许业务为空的字段保留 NULL。
NULL 不能用
=判断,只能IS NULL / IS NOT NULL,容易写出错误 SQL。
5. CHECK 约束(业务域校验)
MySQL8.0 才完整支持 CHECK,5.7 语法兼容但不生效。
✅ 使用建议
用于简单常量规则:状态取值范围、数值范围,作为兜底校验。
CHECK(status IN(0,1,2)), CHECK(amount >=0)
复杂业务逻辑不要交给 CHECK,复杂判断放应用层。
❌ 不适合
需要关联其他表、子查询的校验,CHECK 做不到,只能应用层实现。
6. DEFAULT 默认值约束
✅ 最佳实践
配合
NOT NULL使用,避免插入时漏写字段报错。时间字段:创建时间
DEFAULT CURRENT_TIMESTAMP,更新时间DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP。数值默认 0,字符串默认
'',尽量避免默认 NULL。
⚠️注意
默认值仅在插入不指定该字段才生效;update 设置 null 会覆盖默认值。
7. 约束整体通用优化原则
7.1 分层思想:约束是兜底,不是业务主逻辑
第一层:应用层校验(优先) 参数校验、幂等、业务规则;
第二层:数据库约束兜底,防止 bug、人工导入数据产生脏数据;
不能反过来:不要把全部业务规则丢给数据库约束,会严重影响并发性能。
7.2 性能权衡
所有约束本质上:写入 (INSERT/UPDATE/DELETE) 需要额外校验,牺牲写性能换取读的数据正确性。
OLTP 高并发业务:精简约束,优先主键、必要的 NOT NULL、少量 UNIQUE;谨慎外键、少复杂 CHECK。
OLAP 数仓:可以大量约束,读多写少,数据质量优先。
7.3 约束与索引关系
PRIMARY KEY、UNIQUE 自动创建索引;
FOREIGN KEY 要求外键字段必须有索引,否则会锁表;
新增约束时要关注索引数量,索引过多拖慢写入。
7.4 迁移、分库分表场景约束注意
分库分表后:外键、跨库唯一约束数据库层面全部失效,全部交由应用层;
数据导入、同步工具(datax、canal)要兼容约束,否则同步报错。
7.5 异常可观测
约束报错(唯一冲突、外键报错、check 失败)要捕获数据库异常码,不要只返回笼统报错给前端;便于定位是业务 bug 还是重复请求。
8. 选型速查表
9. 落地实践示例(MySQL8.0)
CREATE TABLE `user` (
`id` BIGINT NOT NULL COMMENT '代理主键,雪花id',
`phone` VARCHAR(11) NOT NULL COMMENT '手机号,业务唯一',
`user_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '用户名',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态0正常1禁用',
`balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '余额',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_phone` (`phone`),
CHECK (`status` IN (0,1)),
CHECK (`balance` >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
没有外键;简单 check 做兜底;非空 + 默认值;业务唯一使用唯一索引;代理主键。