MySQL中NULL值的索引处理规则和普通值存在差异,很多查询性能问题都和NULL值的索引使用不当有关,理解其底层逻辑和查询表现对优化数据库性能非常重要。

MySQL对NULL值的索引存储规则
首先我们需要明确,MySQL的B树索引是可以存储NULL值的,不过存储方式和普通值不同。对于允许为NULL的字段,其索引记录中会额外用一个标志位来标识该记录是否为NULL,而不是像普通值那样直接存储字段内容。
这里需要注意不同存储引擎的差异:InnoDB引擎的B树索引会把NULL值当作一个特殊的最小值来处理,所有NULL值会被集中存放在索引的最左侧;而MyISAM引擎的索引中NULL值会被放在索引的末尾。不过日常开发中InnoDB是主流存储引擎,后续分析默认基于InnoDB场景。
不同查询条件下NULL值的索引使用情况
等值查询场景
当查询条件是字段等于NULL时,MySQL可以使用该字段的索引。比如我们有一个用户表,其中email字段允许为NULL,并且给email字段创建了普通索引:
-- 创建测试表
CREATE TABLE user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NULL,
INDEX idx_email (email)
) ENGINE=InnoDB;
-- 插入测试数据,包含NULL值
INSERT INTO user_info (username, email) VALUES
('user1', 'test1@ippipp.com'),
('user2', NULL),
('user3', 'test3@ippipp.com'),
('user4', NULL);
-- 查询email为NULL的记录,会走idx_email索引
EXPLAIN SELECT * FROM user_info WHERE email IS NULL;
执行上面的EXPLAIN语句,可以看到type列是ref,说明索引被正常使用了。但是如果写成WHERE email = NULL,则不会走索引,因为在SQL标准中NULL和任何值比较(包括NULL本身)的结果都是NULL,这种写法本身逻辑就不成立,MySQL不会用索引优化这类查询。
范围查询场景
范围查询中如果条件包含NULL值,索引的使用情况会受查询范围影响。比如查询email < 'test3@ippipp.com',由于InnoDB把NULL当作最小值,所以NULL值也会被包含在结果中,并且这个查询可以使用idx_email索引。
-- 范围查询包含NULL值,走索引 EXPLAIN SELECT * FROM user_info WHERE email < 'test3@ippipp.com';
如果查询条件是email IS NOT NULL,同样可以使用索引,因为索引中存储了所有非NULL值的记录,MySQL可以直接扫描索引获取符合条件的记录。
联合索引中的NULL值场景
如果联合索引中包含允许为NULL的字段,只要查询条件符合最左前缀原则,并且涉及NULL值的判断符合规则,索引依然可以生效。比如我们创建一个联合索引idx_username_email (username, email):
-- 创建联合索引 ALTER TABLE user_info ADD INDEX idx_username_email (username, email); -- 最左前缀匹配,查询username和email为NULL的记录,走联合索引 EXPLAIN SELECT * FROM user_info WHERE username = 'user2' AND email IS NULL;
但是如果查询条件跳过了联合索引的最左列,只查询email相关的NULL条件,那么联合索引就无法生效了。
包含NULL值的索引设计评估要点
在设计包含NULL值的字段索引时,需要从以下几个维度评估合理性:
- 字段NULL值占比:如果字段中NULL值占比超过30%,那么创建索引的收益会比较低,因为大量NULL值会占用索引空间,同时查询时如果频繁查询NULL值或者非NULL值,索引过滤效果有限。这种情况下可以考虑给字段设置默认值,减少NULL值的使用。
- 查询场景匹配度:如果业务中很少查询该字段的NULL值或者非NULL值,那么没有必要为该字段创建索引。只有当查询条件频繁使用该字段的NULL判断或者范围查询时,创建索引才有意义。
- 联合索引的排列顺序:如果联合索引中包含NULL值字段,建议把NULL值占比低的字段放在联合索引的左侧,提升索引的过滤效率。如果NULL值字段经常作为查询条件,也需要把它放在合适的位置符合最左前缀原则。
查询条件编写的注意事项
在编写涉及NULL值的查询时,需要注意以下几点避免索引失效:
- 判断字段是否为NULL时,必须使用
IS NULL或者IS NOT NULL,不要使用= NULL或者!= NULL,后者不仅不会走索引,逻辑上也不符合SQL标准。 - 如果查询条件中同时包含NULL值判断和普通值判断,比如
WHERE email IS NULL OR email = 'test1@ippipp.com',在MySQL 5.7及以上版本中,优化器可能会选择使用索引,但是低版本中可能不会,这种场景下可以通过拆分为两个查询用UNION连接来提升索引使用概率。 - 避免在NULL值字段上使用函数或者运算,比如
WHERE IFNULL(email, '') = '',这种写法会导致索引失效,因为索引存储的是原始NULL值,经过函数处理之后无法和索引记录匹配。
总结
MySQL的B树索引支持存储NULL值,InnoDB引擎下NULL值会被当作最小值存放在索引最左侧,查询时使用IS NULL、IS NOT NULL以及符合规则的范围查询都可以正常使用索引。在设计包含NULL值的索引时,需要结合字段NULL值占比和业务查询场景评估,避免无效索引占用资源。编写查询条件时注意NULL值的判断语法,避免错误写法导致索引失效,从而保障查询性能。