在MySQL的使用过程中,NULL大概是最容易被轻视也最容易造成线上问题的值。它不等于0,不等于空字符串,甚至不等于它自己,任何值与NULL用等号比较,结果都是NULL而不是true或false。当一个函数的入参或者返回值涉及NULL时,很多想当然的写法都会失效。比如用IFNULL处理SUM的结果却依然查出NULL,用CONCAT拼接字段时因为某个字段是NULL导致整个结果变成NULL。这篇文章就来系统地聊聊MySQL中函数返回NULL的常见场景,以及IFNULL和COALESCE这两个函数的正确打开方式。

先搞清楚NULL的三条基本规则
要理解函数为什么会返回NULL,必须先接受MySQL对NULL的处理逻辑。第一条规则:任何值与NULL做比较运算,结果都是NULL,参与比较的双方哪怕完全相同也一样。所以WHERE name = NULL永远查不出数据,必须写成WHERE name IS NULL。第二条规则:NULL参与算术运算,整个表达式结果为NULL,1 + NULL的结果不是1而是NULL。第三条规则:NULL参与大多数字符串函数和日期函数时,返回结果同样是NULL,比如CONCAT('a', NULL)返回NULL而不是'a'。
这三条规则组合起来,就解释了为什么一个字段里存在NULL,往往会像病毒一样沿着表达式链条向外传播。举个实际的例子,订单表里有一个折扣字段discount,部分记录为NULL,你想计算实付金额,写了price * (1 - discount),那么所有discount为NULL的记录算出来的金额全是NULL。更麻烦的是,如果这条SQL被套在外层查询里继续参与计算或比较,NULL还会继续扩散。
还有一个隐蔽的点:NULL在排序和分组时的行为。ORDER BY默认把NULL排在最前面(升序时),GROUP BY会把所有NULL归为同一组。这些行为本身不算错误,但如果你不了解,很容易误判查询结果。
IFNULL和COALESCE到底该怎么选
IFNULL是MySQL提供的双参函数,语法是IFNULL(expr, alt),当expr不为NULL时返回expr,否则返回alt。COALESCE是SQL标准函数,可以接收多个参数,从左到右返回第一个非NULL的值。两者在双参数场景下效果完全一致,但COALESCE的可移植性更好,换到PostgreSQL、Oracle、SQL Server上都能用。
-- 两个参数时效果等价 SELECT IFNULL(nickname, '未设置') FROM users; SELECT COALESCE(nickname, '未设置') FROM users; -- 多级兜底只能用COALESCE SELECT COALESCE(phone, backup_phone, email, '无联系方式') FROM users; -- 用IFNULL写会非常冗长 SELECT IFNULL(IFNULL(phone, backup_phone), email) FROM users;
这里有一个非常经典的坑:用IFNULL处理SUM的结果。当某个分组内没有任何匹配行时,SUM返回的是NULL而不是0。很多人写了IFNULL(SUM(amount), 0)以为万事大吉,但注意IFNULL包住的应该是SUM本身,而不是外面再套一层。顺序写反了,比如SUM(IFNULL(amount, 0)),它只能保证求和前每行不是NULL,如果分组根本没有行,SUM照样返回NULL。严格说这两种写法解决的是不同问题,前者兜底空分组,后者兜底行内NULL,最稳妥的写法是两个都加上。
另一个坑是隐式类型问题。IFNULL和COALESCE的返回类型是根据参数推导的,如果第一个参数是字符串类型的数字,第二个参数是整数,返回值可能被统一转成字符串,后续参与数值比较或索引查找时就可能出问题。建议兜底值的类型和原字段保持一致。
字符串拼接与日期函数中的NULL陷阱
CONCAT是最容易踩NULL坑的函数之一。只要任何一个参数为NULL,整个拼接结果就是NULL。想象一下你要生成一个收货地址:CONCAT(province, city, district, detail),只要detail是NULL,整条地址直接消失。解决方案有两种,一是对每个可能为NULL的字段单独做IFNULL,二是改用CONCAT_WS,它的第一个参数是分隔符,并且会自动跳过NULL参数。
-- CONCAT遇到NULL整体为NULL
SELECT CONCAT('广东省', '深圳市', NULL, '科技园路1号'); -- 返回NULL
-- CONCAT_WS自动忽略NULL参数
SELECT CONCAT_WS('-', '广东省', '深圳市', NULL, '科技园路1号');
-- 结果:广东省-深圳市-科技园路1号日期函数同样不能幸免。DATE_FORMAT(NULL, '%Y-%m-%d')返回NULL,DATEDIFF(NULL, NOW())返回NULL,这在做报表时经常表现为某几行的日期列整块空白。还有STR_TO_DATE解析失败时返回NULL而不是报错,如果你以为它失败会抛异常,那就大错特错了,脏数据会悄悄地以NULL形式溜进你的结果集。稳妥的做法是在外面包一层判断,或者配合NULLIF做前置过滤。
顺带提一下NULLIF,它是反向操作:NULLIF(a, b)在a等于b时返回NULL,否则返回a。常见用途是把空字符串转成NULL,比如NULLIF(trim(name), ''),这样空串和NULL在语义上就统一了,后续处理会清爽很多。
性能与索引层面的隐藏影响
NULL不仅影响正确性,还可能拖慢查询。最典型的场景是在索引列上套IFNULL:WHERE IFNULL(status, 0) = 1这种写法会让索引直接失效,因为对索引列做了函数运算,优化器只能走全表扫描。正确的做法是改写成等价的形式:WHERE status = 1本身就能覆盖status非NULL的行,如果确实需要把NULL行也算进来,用WHERE status = 1 OR status IS NULL,这样两段条件都可以利用索引。
-- 索引失效的写法 EXPLAIN SELECT * FROM orders WHERE IFNULL(status, 0) = 1; -- 能走索引的等价改写 EXPLAIN SELECT * FROM orders WHERE status = 1 OR status IS NULL;
另外要注意可空列对索引统计的影响。MySQL的索引是可以存储NULL的,但大量NULL值会集中在索引的某一侧,可能让优化器对基数估算产生偏差,进而选错执行计划。如果某个字段在业务上大多数时候都没有值,考虑给它设默认值0或空字符串,而不是允许NULL。
聚合场景下还有个性能小技巧:COUNT函数对NULL是免疫的,COUNT(column)只统计该列非NULL的行数,而COUNT(*)统计所有行。如果想知道某列有效数据的条数,直接用COUNT(column)就能省去先过滤再计数的步骤。
总结:一套实用的防坑清单
处理函数返回NULL的问题,核心思路是分层防御。第一层在表设计阶段就尽量明确字段是否真的需要可空,能给默认值就给默认值。第二层在写SQL时,对每一个可能产生NULL的函数调用点保持警惕,尤其是CONCAT、SUM、日期函数这三类重灾区,及时用COALESCE兜底。第三层在性能审查时,检查索引列上有没有套函数,避免为了处理NULL而牺牲索引。
记住几个要点:比较NULL永远用IS NULL和IS NOT NULL;多级兜底优先用COALESCE;拼接字符串优先用CONCAT_WS;SUM的空分组和行内NULL要分别处理;索引列上不要套IFNULL。把这几条落实到位,大部分由NULL引发的线上查询问题都能提前拦截。另外建议在开发环境把SQL_MODE加上严格模式,虽然它不能直接阻止NULL,但能帮你更早发现那些悄悄产生NULL的脏数据。
mysql NULL处理COALESCE函数IFNULL函数修改时间:2026-09-07 08:42:43