由SQL空值 (NULL)引发的逻辑黑洞:从NOT IN失效谈起
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
在数据库开发中,我们经常听到一句话:“尽量把字段设置为 NOT NULL”。这不仅仅是为了节省存储空间或优化索引,更重要的是为了规避 NULL 带来的逻辑混乱。 NULL 在 SQL 标准中并不等于 0,也不等于空字符串 ''。它代表的是 “Unknown”(未知)。 正是这个“未知”属性,打破了编程语言中常见的二值逻辑(True/False),引入了复杂的三值逻辑 (Three-Valued Logic)。 一、 底层逻辑:为什么 NULL != NULL ?在 Java 或 Python 中,null == null 通常为真。但在 SQL 中,这是一个经典的陷阱。 请看以下 SQL 的执行结果: 原理分析: 因为 NULL 代表“未知”。 “一个未知的值”等于“另一个未知的值”吗?数据库无法确定,所以结果依然是“未知”。 在 SQL 的 WHERE 子句中,只有当表达式结果为 True 时,数据才会被返回。False 和 Unknown 都会被丢弃。 这就是为什么 SELECT * FROM users WHERE age != 20 永远查不到 age 为 NULL 的用户。因为 NULL != 20 的结果是 Unknown。 ✅ 正确写法: 必须使用专门的操作符 IS NULL 或 IS NOT NULL,或者使用 COALESCE 函数处理默认值。 二、 致命陷阱:为什么 NOT IN 查不到数据?这是 NULL 导致的最严重、也最难排查的 Bug。 场景复现: 我们需要查询“没有下单记录的用户”。 表 A (users) 有用户 ID:1, 2, 3。 表 B (orders) 有下单用户 ID:1, NULL (脏数据)。 错误的写法: 预期结果: 用户 2, 3。 实际结果: 空集 (Empty Set)。一条数据都查不到。 逻辑推演: NOT IN 本质上是一组 AND 条件的简写。 上述 SQL 等价于: 当判断用户 2 时:
由于最终结果不是 True,该行被过滤。只要子查询中包含任何一个 NULL 值,整个 NOT IN 查询就会彻底失效,返回空结果。 ✅ 正确解法:使用 NOT EXISTS EXISTS 谓词只关心“有没有行”,它不受 NULL 的三值逻辑影响。 或者,如果你非要用 NOT IN,必须在子查询中显式排除 NULL: 但从性能和语义稳健性角度,推荐始终使用 NOT EXISTS。
三、 统计偏差:COUNT(*) 与 AVG() 的数学游戏在使用聚合函数时,NULL 的处理方式同样存在不一致性,极易导致报表数据对不上。 1. 计数的差异
实战场景: 计算“用户手机号填写的覆盖率”。 应使用 COUNT(phone) / COUNT(*),而不是直接看行数。 2. 平均值的陷阱AVG()、SUM()、MAX() 等聚合函数在计算时,会自动忽略 NULL 值。 场景复现: 某部门 3 个人,奖金分别是:1000, 2000, NULL(未发)。 老板问:平均奖金是多少?
✅ 正确写法: 如果业务要求 NULL 视为 0,必须先处理值: 四、 排序的诡异:NULL 在前还是在后?当你对包含 NULL 的列进行 ORDER BY 时,不同的数据库行为不一致:
这会导致 API 返回给前端的列表顺序在不同环境下表现不一致。 ✅ 通用解法: 使用标准语法控制 NULL 的位置: 注:MySQL 原生暂不支持 NULLS LAST 语法,可以使用 ORDER BY -sort_no DESC (针对数字) 或 ORDER BY ISNULL(sort_no), sort_no 来模拟。
总结SQL 中的 NULL 不是“空”,它是“未知”。在处理 NULL 时,请遵循以下三条铁律:
转自https://juejin.cn/post/7594303825014997007 该文章在 2026/1/13 15:48:05 编辑过 |
关键字查询
相关文章
正在查询... |