索引运算与NULL值问题详解
不能参与的"部分索引运算"指什么?
这里的"部分索引运算"指的是索引列在某些特定操作或条件下无法被MySQL优化器有效利用的情况,特别是当字段包含NULL值时。主要包括以下几种情况:
1. 比较运算
中的问题
sql
-- 当字段可能为NULL时,以下比较运算可能无法使用索引:
WHERE indexed_column = NULL -- 错误写法,应该用 IS NULL
WHERE indexed_column != 'value' -- 包含NULL的记录会被排除
2. 数学运算和函数
sql
-- 对索引列进行运算会导致索引失效
WHERE indexed_column + 1 = 10 -- 索引失效
WHERE YEAR(indexed_date_column) = 2023 -- 索引失效
3. 范围查询
中的边界问题
sql
-- NULL值在范围查询中的特殊行为
WHERE indexed_column BETWEEN 10 AND 20 -- NULL值不会被包含
为什么NULL值对索引不利?
1. 存储结构差异
- B+树索引中NULL值会被特殊处理,通常存储在索引的最左侧或最右侧
- 这使得索引结构变得复杂,影响查询效率
2. 统计信息不准确
- MySQL的查询优化器依赖索引统计信息做决策
- NULL值的特殊性质使得统计信息更难准确计算
3. 比较运算的特殊性
- 在SQL中,
NULL = NULL
的结果是UNKNOWN而不是TRUE - 这种三值逻辑(TRUE/FALSE/UNKNOWN)增加了优化难度
实际代码演示
示例1:NULL值导致索引失效
sql
-- 创建测试表
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2),
discount DECIMAL(10,2), -- 允许NULL
INDEX idx_price (price),
INDEX idx_discount (discount)
);
-- 查询1:使用索引(非NULL列)
EXPLAIN SELECT * FROM products WHERE price = 19.99;
-- type: ref, key: idx_price
-- 查询2:索引可能失效(NULL列)
EXPLAIN SELECT * FROM products WHERE discount = 0.5;
-- 如果discount列有很多NULL值,优化器可能选择全表扫描
示例2:IS NULL的特殊处理
sql
-- 查询3:IS NULL查询
EXPLAIN SELECT * FROM products WHERE discount IS NULL;
-- 即使有索引,IS NULL查询效率通常较低
-- type: ref_or_null(特殊访问方法)
示例3:NOT NULL优化
sql
-- 优化后的表结构
CREATE TABLE products_optimized (
id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2) NOT NULL DEFAULT 0,
discount DECIMAL(10,2) NOT NULL DEFAULT 0, -- 不允许NULL
INDEX idx_price (price),
INDEX idx_discount (discount)
);
-- 查询效率更高
EXPLAIN SELECT * FROM products_optimized WHERE discount = 0.5;
-- type: ref, key: idx_discount
最佳实践建议
-
尽量定义NOT NULL约束:
sqlCREATE TABLE users ( id INT NOT NULL, name VARCHAR(100) NOT NULL DEFAULT '' );
-
为NULL列设置合理的默认值:
sqlALTER TABLE products MODIFY discount DECIMAL(10,2) NOT NULL DEFAULT 0;
-
避免对索引列使用函数或运算:
sql-- 不好的写法 SELECT * FROM products WHERE YEAR(created_at) = 2023; -- 好的写法 SELECT * FROM products WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31';
-
使用IS NULL的特殊处理:
sql-- 如果必须查询NULL值,考虑使用覆盖索引 CREATE INDEX idx_discount_covering ON products(discount, name);
-
监控NULL值比例:
sql-- 检查NULL值占比 SELECT COUNT(*) AS total, SUM(CASE WHEN discount IS NULL THEN 1 ELSE 0 END) AS null_count, (SUM(CASE WHEN discount IS NULL THEN 1 ELSE 0 END) / COUNT(*)) AS null_ratio FROM products;
通过避免NULL值和注意索引运算的限制,可以显著提高索引利用率和查询性能。