这个问题非常经典,直接给你结论:

MySQL 中,字段允许为 NULL,索引依然可以正常使用,但在统计、查询、存储和性能上有几个非常重要的“坑”需要注意。
我用最直白的方式给你讲清楚。
只要查询条件写对了,索引就能用。
-- 表 t,字段 name 允许 NULL,name 上有索引SELECT * FROM t WHERE name = '张三'; -- ✅ 可以用索引SELECT * FROM t WHERE name IS NULL; -- ✅ 也可以用索引(这个很多人误以为不行)
关键点:IS NULL 在索引中是有记录的,MySQL 会把 NULL 值也存进索引里(放在最前面或最后面,取决于引擎和排序方式)。
这是最容易踩的坑,因为 NULL 在聚合函数里会被忽略。
| 操作 | 结果 | 原因 |
|---|---|---|
| COUNT(name) | 只统计 非 NULL 的行数 | NULL 不算 |
| COUNT(*) | 统计所有行 | 不受 NULL 影响 |
| SUM(age) | 忽略 NULL 值 | NULL 不参与计算 |
| AVG(age) | 只算非 NULL 的平均值 | 分母不包含 NULL 的行 |
| DISTINCT name | 会把 NULL 当作一个独立值 | 多个 NULL 只算一个 |
示例:
-- 表里有 10 行,其中 3 行的 name 是 NULLSELECT COUNT(name) FROM t; -- 结果是 7,不是 10 ❗SELECT COUNT(*) FROM t; -- 结果是 10 ✅
教训:如果你想要统计所有行,用 COUNT(*) 或 COUNT(主键),别用 COUNT(可为 NULL 的字段)。
ORDER BY 的结果顺序。假设有组合索引 (a, b),两个字段都允许 NULL:
-- 数据:(1, 1)(1, NULL)(NULL, 2)(NULL, NULL)
索引能查到什么?
| 查询 | 是否走索引 | 说明 |
|---|---|---|
| WHERE a = 1 | ✅ 走索引 | 正常 |
| WHERE a IS NULL | ✅ 走索引 | NULL 在索引中有记录 |
| WHERE a = 1 AND b IS NULL | ✅ 走索引 | 组合索引完全匹配 |
| WHERE b = 2 | ❌ 不走索引 | 因为 b 是组合索引的第二列,不能跳过 a 单独查 b |
核心:组合索引中,NULL 值也参与索引构建,但最左前缀原则依然生效,不受 NULL 影响。
SELECT * FROM t WHERE name NOT IN ('张三', '李四');如果 name 允许 NULL,这个查询会漏掉 name = NULL 的行!
原因:NULL 和任何值比较都是 UNKNOWN(既不是 TRUE 也不是 FALSE),所以 NOT IN 会排除掉所有含 NULL 的行。
正确做法:
SELECT * FROM t WHERE name NOT IN ('张三', '李四') OR name IS NULL;| 问题 | 结论 |
|---|---|
| 允许 NULL 的字段能建索引吗? | ✅ 可以 |
| IS NULL 能走索引吗? | ✅ 可以 |
| 索引中 NULL 占空间吗? | ✅ 占,略大一点 |
| COUNT(字段) 会统计 NULL 吗? | ❌ 不会,只统计非 NULL |
| NOT IN 会包含 NULL 吗? | ❌ 不会,需要额外加 OR IS NULL |
| 唯一索引允许多个 NULL 吗? | ✅ 允许,多个 NULL 不冲突(因为 NULL != NULL) |
能设置 NOT NULL + 默认值,就尽量别允许 NULL。
原因:
如果业务上确实需要表示“未知/无值”,那允许 NULL 也可以,但写 SQL 时一定要留意上述坑。