mysql字段应设为not null,因null不存入b+树索引、导致全表扫描,扭曲统计信息、降低查询性能,增加存储开销,且易引发逻辑错误;仅当语义确需区分“未知”与“空值”时才用null。

MySQL 字段设为 NOT NULL 不是为了“强制规范”或“看起来干净”,而是因为 NULL 会真实改变索引结构、统计信息和查询执行路径——尤其在你真正在意性能、准确性和可维护性时,它不是可选项,是必选项。
为什么 B+ 树索引里查不到 IS NULL 的记录
InnoDB 的 B+ 树索引不存储 NULL 值。不是“跳过”,是压根不写进去。所以当你执行 WHERE col IS NULL,优化器只能全表扫描——哪怕 col 上建了索引也白搭。
更隐蔽的问题是:WHERE col = ? 在 col 允许 NULL 时,优化器可能直接弃用索引,尤其当 SHOW INDEX 显示该列 Null 列值为 YES,且实际数据中 NULL 占比略高(比如 >5%)时,成本估算会倾向走全表。
-
EXPLAIN中key_len比预期大 1 字节?那是NULL位图标记位在起作用 - 复合索引如
(a, b),若b允许NULL,WHERE a = 1 AND b > 10可能无法下推b的范围查找 -
UNIQUE索引下,NULL被特殊豁免:多行同时为NULL不报错,但业务上这常是漏洞(比如多个用户共享空邮箱)
NULL 字段如何悄悄拖慢查询和存储
每行对每个可为 NULL 的列,InnoDB 都要分配 1 bit(按字节对齐,最多 8 列共用 1 字节)。1000 万行表,10 个可空列,就多占约 1.2MB —— 看似不多,但会影响页内记录密度,间接增加 I/O 和缓存压力。
更关键的是统计信息失真:NULL 让优化器难以准确估算选择率。比如 COUNT(col) 自动忽略 NULL 行,而 COUNT(*) 统计所有行;NOT IN (subquery) 遇到任意 NULL 就整个返回空集;ORDER BY col 会让所有 NULL 排最前,跟业务语义常常冲突。
-
GROUP BY或DISTINCT时,所有NULL被视为“相等”,容易掩盖数据异常 -
col + 1、CONCAT('a', col)等表达式只要col是NULL,结果全为NULL - ORM 映射(如 Django、MyBatis)对
NULL的默认处理常与业务预期不一致,需额外判空逻辑
什么时候真该用 NULL?以及怎么安全替换
只有两个场景值得认真考虑 NULL:字段语义上确实存在“未知/不适用”状态,且该状态必须与“已知为空”严格区分。典型例子:deleted_at(软删除时间)、reviewed_at(审核通过时间)、mediator_id(中介 ID,部分订单无中介)。
其他情况,一律优先用 NOT NULL DEFAULT ''(字符串)、NOT NULL DEFAULT 0(数字)、NOT NULL DEFAULT '1970-01-01'(日期)等确定值。它们可被完整索引、参与比较、支持等值查询,且无需额外元数据开销。
- 从
NULL改成NOT NULL必须先UPDATE所有空值,否则ALTER TABLE ... SET NOT NULL直接报错 -
DEFAULT ''不等于“随便填”,它让字段语义清晰:“该字段有值,只是当前为空” - 迁移时注意锁表现象:全表
UPDATE可能触发页分裂,建议在低峰期分批执行
真正难的不是改语法,是厘清字段背后的业务含义——NULL 是数据库里的一个值,但它是模糊的、传染的、不可控的;而 NOT NULL 强制你在设计阶段就回答一个问题:这个字段,到底有没有意义?











