MySQL索引列允许NULL会导致查询失效:B+树不存NULL值,IS NULL只能全表扫描,=查询可能弃用索引;UNIQUE索引允许多NULL但易引发业务漏洞;复合索引中NULL列削弱范围查找能力;NOT NULL + DEFAULT ''更省空间、语义清晰且避免迁移风险;仅当需区分“未填写”与“明确为空”时才用NULL,如deleted_at;ALTER TABLE加NOT NULL需先填充数据,否则报错。

索引列允许 NULL 时,查询会悄悄失效
MySQL 的 B+ 树索引默认不存储 NULL 值——不是“跳过”,而是压根不写进索引结构。这意味着 WHERE col IS NULL 只能走全表扫描;而更隐蔽的问题是:WHERE col = ? 在 col 允许 NULL 时,优化器可能放弃使用该索引,尤其当统计信息显示 NULL 占比高时。
-
UNIQUE索引下,NULL被特殊对待:多行可以同时为NULL,不违反唯一性——但业务上这常是逻辑漏洞(比如多个用户共用一个空手机号) - 复合索引如
(a, b),若b允许NULL,则WHERE a = 1 AND b > 10可能无法利用b的范围查找能力 - 执行
EXPLAIN时注意key_len值:含 NULL 列的索引记录会多出 1 字节标记位(InnoDB 的 NULL 位图),虽小但累积影响存储密度
NOT NULL + DEFAULT '' 比放任 NULL 更省空间也更稳
很多人以为设成 NOT NULL DEFAULT '' 是“妥协”,其实这是对 InnoDB 存储格式的合理适配。空字符串 '' 是确定值,可被索引完整收录、参与比较、支持等值查询,且无需额外元数据开销。
- InnoDB 每行对每个可为 NULL 的列分配 1 bit(按字节对齐,最多 8 列共用 1 字节),1000 万行表就多占约 1.2MB;全
NOT NULL则完全省掉这部分 -
DEFAULT ''不等于“随便填”,它让字段语义清晰:“该字段有值,只是当前为空”;而NULL表示“未知/不适用”,两者在业务校验、API 返回、ORM 映射中行为完全不同 - 避免后续迁移成本:从
NULL改成NOT NULL需全表 UPDATE 所有 NULL 行,锁表时间长,还可能触发页分裂
哪些情况真得留 NULL?别硬套教条
不是所有字段都适合塞默认值。关键看业务是否需要区分「未填写」和「明确为空」这两个状态。
- 例如
deleted_at时间戳:NULL 表示“未删除”,非 NULL 表示“软删时间”——这里NULL是核心语义,强行改成NOT NULL DEFAULT '0000-00-00 00:00:00'会破坏逻辑 - 又如
invite_code:新用户注册时确实没有邀请码,此时NULL比''更准确反映“尚未生成/未提供” - 但要注意:一旦选了
NULL,所有 SQL 查询必须显式处理,比如WHERE invite_code != 'ABC'会漏掉invite_code IS NULL的行——这个坑,90% 的人第一次都踩过
ALTER TABLE 加 NOT NULL 时最容易卡住的点
线上表加 NOT NULL 约束不是执行一条语句就完事,它本质是一次隐式全量数据校验。
- 如果表里已有
NULL值,ALTER TABLE t MODIFY c VARCHAR(20) NOT NULL会直接报错:ERROR 1265 (01000): Data truncated for column 'c' at row 1 - 安全做法分两步:先用
UPDATE填充默认值(注意 WHERE 条件别漏掉 NULL),再加约束;大表务必在低峰期操作,并监控innodb_row_lock_waits - MySQL 8.0+ 支持
ALGORITHM=INSTANT,但仅适用于添加列或修改列类型等轻量操作;加NOT NULL约束仍需拷贝表(COPY算法),别被文档误导
NOT NULL,而是判断哪一列“真的不能没有值”——这得翻需求文档,而不是只看字段名。


















