生产环境必须设not null——is null必全表扫描,等值查询可能弃用索引,复合索引范围查找降级,空值语义引发orm、api、聚合函数连锁问题,仅deleted_at等极少数场景需null。

生产环境里,NOT NULL 不是“建议”,而是事实上的强制要求——不设它,等于主动给查询、索引、存储和业务逻辑埋雷。
IS NULL 查询必然全表扫描
InnoDB 的 B+ 树索引压根不存 NULL 值。不是跳过,是根本不写进去。所以哪怕你在 status 列加了索引,WHERE status IS NULL 也一定触发全表扫描。
验证方式很简单:EXPLAIN 看 key 字段为空、type 是 ALL,就是典型信号。
- 你无法靠加索引修复这个问题;补救方案(比如建计算列)是设计缺陷的兜底,不是正解
- 哪怕该列
NULL比例只有 0.1%,优化器依然不会为IS NULL走索引——没得选
等值查询可能悄悄弃用索引
字段允许 NULL 时,WHERE col = 'active' 看似合理,但优化器会因基数估算失真而放弃索引。
原因在于:统计信息中 NULL 占比一旦偏高(比如 >5%),优化器就倾向认为“等值匹配筛不掉多少行”,转而选全表或别的索引。
-
EXPLAIN中key_len比预期大 1,说明用了 NULL 位图,这是隐式成本提示 - 执行
ANALYZE TABLE后再EXPLAIN,对比rows预估是否突变,就能验证是否被干扰 - 这种失效不报错、不告警,只在慢查询日志里静默出现
复合索引范围查找能力直接降级
联合索引如 (user_id, created_at),如果 created_at 允许 NULL,那么 WHERE user_id = 123 AND created_at > '2025-01-01' 很可能无法利用 created_at 的有序性做索引下推。
InnoDB 要求范围查询的右列必须“确定可比较”,而 NULL 在排序中排最前,且不参与大小比较逻辑。
- 优化器可能直接降级为只用
user_id单列查找,后面靠回表过滤 - 修复不是删数据,而是
ALTER TABLE t MODIFY created_at DATETIME NOT NULL DEFAULT '1970-01-01',再补默认值 - 注意:
ALTER TABLE ... MODIFY ... NOT NULL必须先 UPDATE 所有NULL行,否则报错;线上操作要评估锁表时间
空值语义混乱 + ORM/应用层连锁踩坑
NULL 表示“未知”,'' 或 0 表示“已知为空/零”。这个区别在应用层会层层放大:
-
NOT IN遇到NULL直接返回空结果,!=同样漏数据——90% 的人第一次都踩过 - ORM 映射时,
NULL可能转成null对象,引发空指针;而''是稳定字符串 - API 返回 JSON 时,
NULL字段可能被省略,前端取值逻辑崩掉;DEFAULT ''则始终存在且可控 - 聚合函数如
COUNT(col)自动忽略NULL,但业务上往往需要的是“所有行”而非“非空行”
真正需要 NULL 的场景极少,比如 deleted_at(NULL = 未删除,非 NULL = 软删时间),这种语义无法用默认值替代。除此之外,NOT NULL + DEFAULT 不是妥协,是对 InnoDB 存储格式和业务真实需求的对齐。











