MySQL数据完整性靠约束强制保障而非人工校验;主键和唯一约束防并发重复;外键防孤儿记录且需InnoDB支持;NOT NULL、DEFAULT、CHECK需语义准确;约束过多会降低写入性能。

MySQL 数据完整性不是“有没有数据”,而是数据是否准确、一致、合法、可信赖——它靠约束条件来强制保障,不是靠人写代码时小心谨慎。
主键和唯一约束:为什么不能只靠程序校验?
主键(PRIMARY KEY)和唯一约束(UNIQUE)解决的是“重复”问题。比如用户注册时用程序查一遍 username 是否存在,再插入——这在并发场景下必然漏掉竞态:两个请求几乎同时通过检查,然后都插入成功。
-
PRIMARY KEY=NOT NULL+UNIQUE,且一个表只能有一个 -
UNIQUE列允许NULL,但多个NULL不算重复(MySQL 行为) - 复合唯一约束要写成表级:
CREATE TABLE orders ( user_id INT, order_no VARCHAR(20), UNIQUE (user_id, order_no) );
- 别指望应用层“先查后插”替代唯一约束——它不原子,也不防并发
外键约束:关联表之间谁该删、谁该留?
外键(FOREIGN KEY)不是锦上添花的功能,它是防止“孤儿记录”的最后一道防线。比如删除客户前没清理订单,就会留下指向不存在客户的 customer_id。
- 启用外键需存储引擎为
InnoDB(MyISAM不支持) - 被引用的列必须有索引(通常是主键或唯一索引),否则建外键会失败
- 级联行为要显式声明:
ON DELETE CASCADE或ON DELETE RESTRICT,默认是RESTRICT - 线上环境慎用
CASCADE:一个DELETE可能触发多层隐式删除,难以追溯
非空、默认值与检查约束:哪些字段真该强制?
NOT NULL 和 DEFAULT 看似简单,但错配会导致数据“看似存在,实则无效”。例如 status TINYINT DEFAULT 0,但业务中 0 并不表示“未设置”,而是“已取消”——这就让默认值失去语义。
-
NOT NULL应用于真正不可缺失的字段(如created_at、user_id),而非所有字段都加 -
DEFAULT推荐用确定性表达式:CURRENT_TIMESTAMP比'1970-01-01'更可靠 -
CHECK约束从 MySQL 8.0.16 起才完整支持(早期版本会解析但忽略),例如:CREATE TABLE products ( price DECIMAL(10,2), CHECK (price >= 0) );
- 避免用
CHECK做复杂逻辑(如正则邮箱校验),性能差且难调试;这类应交由应用层或触发器
约束不是越多越好:它们怎么悄悄拖慢你的 SQL?
每个约束都是实时校验点。主键索引加速查询,但也让每次 INSERT/UPDATE 多一次 B+ 树查找;外键会锁住被引用行(即使是 SELECT),引发死锁风险;CHECK 在批量导入时可能成为瓶颈。
- 高频写入表(如日志、埋点)通常禁用外键,靠应用层保证关系
- 临时表、ETL 中间表可先去掉约束,等数据清洗完成再加回
- 用
SHOW CREATE TABLE t查看真实生效的约束,注意某些 ORM 或迁移工具会静默跳过不支持的约束(比如旧版 MySQL 忽略CHECK) - 约束名最好自定义(如
fk_orders_customer_id),否则报错信息里只显示系统生成的乱码名,排查困难
约束真正的价值,不在“加了就安全”,而在“没它就一定出错”。很多线上数据异常,追根溯源都是某张表少了一个 NOT NULL,或外键没建导致关联断裂——这种问题不会立刻报错,但会在某个报表、某次导出、某条聚合查询里突然崩出来。










