NOT NULL约束必须由数据库引擎强制执行才能真正起效,仅字段定义中声明不够;新增非空字段需配合DEFAULT或分步UPDATE+SET NOT NULL;须查系统表验证、警惕ORM绕过及批量导入跳过校验。

NOT NULL 约束为什么不能只加在字段定义里?
加 NOT NULL 是最基础的数据质量防线,但很多人只在建表时写进字段定义,后续业务变化后忘了补约束,导致空值悄悄混入。真正起效的前提是:它必须被数据库引擎强制执行,而不是靠应用层“自觉不传 null”。
实操建议:
- 新增字段带
NOT NULL时,若表非空,必须同时指定DEFAULT值或先用ALTER TABLE ... ADD COLUMN加可空字段,再用UPDATE填值,最后ALTER COLUMN ... SET NOT NULL - 检查已有列是否真被约束:查询系统表,比如 PostgreSQL 用
SELECT column_name, is_nullable FROM information_schema.columns WHERE table_name = 'xxx';MySQL 查DESCRIBE table_name中的Null列 - 注意 ORM 框架(如 Django、Hibernate)可能绕过数据库级约束,直接插入
NULL—— 要确认其生成的 DDL 包含NOT NULL,且未设置allow_null=True类似开关
CHECK 约束在不同数据库中的行为差异
CHECK 是表达业务规则最直接的方式,比如 age BETWEEN 0 AND 150 或 email LIKE '%@%',但它在各数据库中支持度和默认行为差别很大。
常见问题与应对:
- MySQL 5.7 及以前会解析
CHECK但完全不执行,8.0.16+ 才真正生效;PostgreSQL 和 SQL Server 默认严格校验;SQLite 需启用PRAGMA ignore_check_constraints = OFF(默认已开启) - PostgreSQL 允许在
CHECK中调用函数(如length(phone) = 11),但 MySQL 不支持函数表达式,只能用确定性标量运算 - 批量导入数据(
LOAD DATA INFILE或COPY)可能跳过CHECK,PostgreSQL 的COPY会校验,MySQL 的LOAD DATA在严格模式下才报错
外键约束失效的三个隐蔽原因
外键本该保证引用完整性,但线上常出现“子表有记录,父表主键却没了”的情况,多数不是没加约束,而是约束被悄悄禁用或绕过。
排查重点:
- MySQL 中
FOREIGN_KEY_CHECKS = 0会被某些迁移工具或 DBA 临时关闭,之后忘记恢复;可通过SELECT @@FOREIGN_KEY_CHECKS实时确认 - 字段类型不严格一致:比如父表
id BIGINT UNSIGNED,子表parent_id BIGINT(无符号缺失),MySQL 会静默忽略外键,不报错也不生效 - 索引缺失:外键列必须有索引(单列或作为前导列),否则 PostgreSQL 和 MySQL 都拒绝创建;但有些旧版本 MySQL 允许创建无索引外键,运行时性能极差且校验不可靠
UNIQUE 约束与业务唯一性之间的 gap 怎么填?
UNIQUE 能防重复,但真实业务中“唯一”往往有条件,比如“每个用户每种通知类型最多一条未读记录”,这种复合逻辑无法靠单个 UNIQUE 约束覆盖。
更可靠的组合方式:
- 用部分索引(PostgreSQL)或函数索引(Oracle/PostgreSQL)实现条件唯一:例如
CREATE UNIQUE INDEX idx_uniq_unread ON notifications (user_id, type) WHERE status = 'unread' - 避免用
UNIQUE替代业务逻辑判断:比如注册时查email是否存在,再插入 —— 这中间有竞态窗口;应改用INSERT ... ON CONFLICT DO NOTHING(PostgreSQL)或INSERT IGNORE(MySQL),让约束本身兜底 - 注意 NULL 处理:多数数据库中,
UNIQUE约束允许多个NULL(因 NULL ≠ NULL),如果业务要求“邮箱为空也算唯一”,得用函数索引把NULL映射为固定字符串再约束










