把数据库里一条用户记录取出来,空字中间名那一栏是符串空的。你问它到底是空字“没有中间名”还是“中间名就是空字符串”,它给你的符串答案可能完全不一样。这不是空字文字游戏,是符串真实项目里经常踩的坑。
null 在大多数编程语言和数据库里表示“没有值”。空字注意,符串不是空字“值为空”,是符串压根不存在这个值。空字符串 '' 则是空字一个实实在在的值,只不过它的符串长度为 0。打个比方:null 像一个没写字的空字便利贴,你连便利贴都没有;空字符串像一张写了字但字被擦掉的符串便利贴——便利贴还在,只是空字内容为空。
我在一个电商项目里见过这个问题。用户表有个 coupon_code 字段,代表用户领的优惠券码。运营同事跑报表要统计“有多少用户没领券”。开发写的 SQL 是 WHERE coupon_code = '',结果查出来 0 条。原因很简单:没领券的用户,这个字段存的是 null,不是空字符串。改成 WHERE coupon_code IS NULL 才拿到真实数据。反过来,如果某次批量导入把空值写成了 '',那 IS NULL 又查不到它们。两种“空”在数据库里是两条平行线,互不交叉。
这种差异在查询条件里更明显。WHERE name = '' 能匹配到空字符串的行,但匹配不到 null 的行。而 WHERE name != '' 也匹配不到 null 的行——因为 null 参与任何比较运算,结果既不是 true 也不是 false,而是 unknown。SQL 的三值逻辑在这里体现得淋漓尽致。很多人第一次遇到“为什么 != '' 漏掉了 null 行”时都会愣一下。
不同数据库对这两者的处理也有微妙差别。Oracle 曾经把空字符串直接当作 null 处理,导致 INSERT INTO t VALUES ('') 实际存进去的是 null。这个行为让不少从 MySQL 迁移到 Oracle 的团队吃过亏。MySQL 则严格区分二者,'' 就是 '',null 就是 null。PostgreSQL 同样区分,而且对 null 的处理更严谨。写跨库兼容的代码时,这一点必须心里有数。
编程语言层面同样如此。Java 里 String s = null; 和 String s = ""; 是两回事。调 s.length(),前者直接抛 NullPointerException,后者返回 0。JavaScript 里 null == '' 是 false,但 null == undefined 是 true,'' == 0 又是 true——类型转换的坑一层套一层。Python 里 None 和 '' 在布尔上下文中都是 falsy,但 None is '' 返回 False,用 is 比较时泾渭分明。
接口设计时这个区别更关键。假设一个更新用户资料的 API,nickname 字段传 null 表示“不修改昵称”,传空字符串表示“把昵称清空”。如果后端不区分,用户想清空昵称却怎么也清不掉,或者不想改昵称却被意外清空了。这种 bug 不报错,但用户体验极差。我见过一个团队为此专门在 API 文档里用加粗字体标注:null 等于不更新,空字符串等于更新为空。
JSON 里也一样。{ "name": null} 和 { "name": ""} 序列化出来不同,反序列化后语义不同。前端拿到 null 可能显示“未设置”,拿到空字符串可能显示一片空白。如果前端不做区分,用户看到的就是一样的空白,但背后的数据状态完全不同。
说到底,null 是“无”,空字符串是“空”。一个关于存在性,一个关于内容。写代码时多问一句“这个字段到底有没有值”,能省掉后面很多调试时间。数据库设计阶段就该想清楚:这个列允许 null 吗?默认值是空字符串还是 null?业务上“没填”和“填了但为空”是不是同一件事?想清楚了,后面写查询、写接口、写前端展示都会顺畅很多。