大家好呀,我是小米,今年31岁,一个在互联网搬砖十年的老程序员。写代码写久了,发现自己越来越像一个“数据库保姆”——一会儿要帮表加个索引,一会儿要优化SQL,甚至连表结构定义的时候,我都忍不住要多啰嗦几句。
最近有个小伙伴来问我:“小米,MySQL 里面,为什么很多人建议字段尽量定义成 NOT NULL,而不是随便就给个 NULL 呢?我之前面试被问到这个问题,脑子一懵,只回了句‘因为更规范’……”
我听完笑了半天,这个回答确实很“官方”,但面试官要的不是套话,而是背后原理。于是,我给他讲了一个真实的故事。今天也分享给大家,希望你下次面试再被问到时,不会再只说“规范”了。
那个被 NULL 坑惨的项目
我第一次真正意识到 NULL 的“威力”,是在五年前。那时候我在一家创业公司,做一个电商系统。订单表设计得很随意,几乎所有字段都允许 NULL。
当时大家觉得:
- 不填就用 NULL,多方便;
- 反正业务逻辑里也会判断;
- 万一以后字段不用了,也能用 NULL 表示“不存在”。
看上去很自由对吧?结果悲剧了。
有一次,业务部门说:“小米,帮我们跑个统计,看看上个月有多少订单用了优惠券。”
我心想简单,写个 SQL:
结果一跑,数字对不上。为啥?因为 coupon_id 里有些是 NULL,!= '' 对 NULL 是不成立的。MySQL 遇到 NULL 会直接返回 UNKNOWN,结果这些行就被排除掉了。
最后我不得不把 SQL 改成:
是不是很麻烦?当时我心里就骂了一句: “早知道当初就定义成 NOT NULL,加个默认值多好!”
NOT NULL 背后的几个硬核理由
好了,故事讲完,咱们来拆解面试官到底想要什么答案。其实 MySQL 官方和很多大厂的 DBA 团队,都有明确的最佳实践:字段尽量用 NOT NULL,除非业务必须允许 NULL。
为什么?原因主要有五个。
NULL 会影响索引和查询优化
在 MySQL 的 B+Tree 索引里,NULL 是一个特殊值,它既不等于任何值,也不大于或小于某个值。于是,涉及 NULL 的比较往往需要额外的判断逻辑。
比如说:
- col = 5 走索引没问题;
- col IS NULL 也能走索引,但优化器处理逻辑更复杂;
- col != 5 或 col <> 5,如果列里有 NULL,就会导致结果不可预测,优化器甚至可能放弃走索引。
这意味着什么?同样一张表,允许 NULL 和不允许 NULL,性能上可能就差了一个量级。
大厂的 DBA 通常会告诉你:在高并发场景下,NOT NULL + 合理默认值,比 NULL 要更稳定。
NULL 会让聚合函数结果“失真”
还记得刚才我那个优惠券统计的坑吗?这就是 NULL 导致聚合统计不准的典型例子。
举个简单例子:
如果 score 里有 NULL,MySQL 会自动忽略这些行。结果就是平均分被抬高了。
而如果你希望缺考的同学分数算 0 呢?抱歉,你必须用 IFNULL(score, 0)。
所以,NULL 带来了额外的心智负担:你必须随时想一想,这个聚合是不是忽略了 NULL,这个比较是不是排除了 NULL。
NULL 会浪费存储空间
很多人以为 NULL 不占空间,其实并不是这样。
InnoDB 在每一行都会有一个 NULL 值标志位,用来记录该列是否为 NULL。一旦表里有大量可为 NULL 的列,这个开销就上去了。
尤其是在大表里,哪怕只是多浪费几个字节,放大到几亿行数据,那也是实打实的磁盘和内存消耗。
而如果你把字段定义为 NOT NULL DEFAULT 0,InnoDB 直接就存一个数值,不需要额外标志位。
NULL 会让代码逻辑更复杂
我在代码里见过最魔幻的一幕:
是不是很眼熟?程序员不得不同时判断 NULL 和正常值,逻辑绕来绕去,Bug 也多。
而如果一开始就约定:年龄字段必须是 NOT NULL,没填就存 0,业务里“0”表示“未知”或“未填写”,岂不是更清晰?
所以,NOT NULL 其实是对业务逻辑的简化。
NULL 和唯一索引的“诡异行为”
在 MySQL 里,唯一索引 (UNIQUE) 对 NULL 的处理很特别:
- NULL 和 NULL 不相等。
- 这意味着唯一索引可以存多行 NULL。
比如,你有个唯一索引的手机号字段,结果一堆 NULL 插进去也不会报错。
这是不是很违背直觉?如果你面试的时候能举出这个例子,面试官一定眼前一亮。
那是不是所有字段都要 NOT NULL?
到这里,有小伙伴可能要问:那是不是以后表结构设计里,所有字段都要 NOT NULL?
我一般会说:大多数字段是的,但也要看业务场景。
哪些字段适合 NOT NULL?
- 业务核心字段(比如订单号、用户ID、金额、状态)。
- 外键字段(比如 user_id、product_id)。
- 经常作为查询条件的字段(保证索引更好用)。
哪些字段可以允许 NULL?
- 确实业务上“不存在”是一种状态,比如:
-
- 用户的中间名(Middle Name);
- 删除时间(deleted_at,如果没删除就 NULL);
- 可选的备注信息。
但即便如此,我还是建议你在设计时多想一步:是否可以用默认值替代 NULL?比如 deleted_at = 0 表示未删除。
面试官期待的“完美答案”
如果你在面试里遇到这个问题,可以这样回答:
字段尽量定义成 NOT NULL,主要有几个原因:
避免 NULL 导致索引失效和查询优化复杂化;
聚合函数遇到 NULL 会让结果失真,需要额外处理;
NULL 需要额外存储空间,不如 NOT NULL 高效;
NULL 会增加代码逻辑复杂度;
唯一索引允许多行 NULL,可能违背预期。
当然,不是所有字段都必须 NOT NULL,如果业务上确实需要“不存在”这个状态,可以允许 NULL,但要谨慎使用。
这样回答,面试官会觉得你不仅懂规范,还能举例、讲原理,妥妥加分。
建议
作为一个在数据库里摸爬滚打了十年的老兵,我的建议很简单:
- 设计表结构的时候,先假设所有字段都是 NOT NULL;
- 再去思考:有没有必须用 NULL 才能表达的场景;
- 如果有,那就留给 NULL,否则就定义默认值。
很多时候,表结构的规范化决定了系统未来的上限。你现在偷懒,未来踩坑的就是自己。
结语
回想当年那个订单表,我真是被 NULL 教训过一次之后,才开始意识到数据库设计不是随便拍脑袋的事。
如今再遇到新人问我“为什么要 NOT NULL”,我都会笑着说:
“这不仅仅是规范,更是性能、存储、逻辑、可维护性的综合考量。”
END
写到这儿,希望这篇文章能帮你在下次面试的时候,多加几分底气,也希望你在写表结构时,能少掉进 NULL 的坑里。
毕竟,数据库设计这事儿,真的是“细节决定成败”。
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!