Mysql索引失效的情况
MySQL 索引失效是影响查询性能的常见问题,指优化器本可使用索引却选择全表扫描的情况。以下是索引失效的常见场景及底层原因详解:
一、索引字段参与运算或函数操作
原理:索引存储的是字段原始值,若对字段进行运算或函数处理,优化器无法直接匹配索引值,只能全表扫描后再计算。
示例:
-- 1. 字段参与运算
SELECT * FROM users WHERE age + 1 = 30; -- 失效(age参与运算)
-- 正确写法:SELECT * FROM users WHERE age = 29;-- 2. 字段使用函数
SELECT * FROM users WHERE SUBSTR(name, 1, 1) = '张'; -- 失效(name被函数处理)
-- 优化方案:使用前缀索引(CREATE INDEX idx_name ON users(name(1));)-- 3. 日期函数滥用
SELECT * FROM orders WHERE DATE(create_time) = '2023-10-01'; -- 失效
-- 正确写法:SELECT * FROM orders WHERE create_time BETWEEN '2023-10-01 00:00:00' AND '2023-10-01 23:59:59';
二、模糊查询前缀含 %
原理:B+ 树索引是有序的,LIKE '%xxx'
或 LIKE '%xxx%'
无法通过索引有序性定位,只能全表扫描;而 LIKE 'xxx%'
可利用前缀匹配走索引。
示例:
-- 失效场景
SELECT * FROM users WHERE name LIKE '%三'; -- 前缀含%
SELECT * FROM users WHERE name LIKE '%三%'; -- 前后都含%-- 有效场景
SELECT * FROM users WHERE name LIKE '张%'; -- 仅后缀含%,可走索引
三、隐式类型转换
原理:字段类型与查询值类型不匹配时,MySQL 会自动转换类型(如字符串转数字),相当于对字段进行函数操作,导致索引失效。
示例:
-- 表结构:phone 是 VARCHAR 类型,且有索引
SELECT * FROM users WHERE phone = 13800138000; -- 失效(数字转字符串,相当于函数操作)
-- 正确写法:SELECT * FROM users WHERE phone = '13800138000'; -- 类型一致
四、使用 OR
连接非索引字段
原理:OR
两侧若有一个字段无索引,优化器会认为全表扫描比部分走索引更高效(需合并两次结果),导致索引失效。
示例:
-- id 有索引,name 无索引
SELECT * FROM users WHERE id = 10 OR name = '张三'; -- 失效(name无索引)
-- 优化方案:
-- 1. 给 name 加索引;
-- 2. 拆分为两个查询:SELECT * FROM users WHERE id = 10 UNION SELECT * FROM users WHERE name = '张三';
五、联合索引违反 “最左前缀原则”
原理:联合索引 (a, b, c)
的生效顺序为 a
→ a+b
→ a+b+c
,缺少最左字段 a
时,索引整体失效。
示例:
-- 联合索引:(user_id, status, create_time)
SELECT * FROM orders WHERE status = 1; -- 失效(缺少最左 user_id)
SELECT * FROM orders WHERE status = 1 AND create_time > '2023-01-01'; -- 失效
SELECT * FROM orders WHERE user_id = 100 AND create_time > '2023-01-01'; -- 部分生效(仅 user_id 走索引,create_time 不生效)
SELECT * FROM orders WHERE user_id = 100 AND status = 1; -- 完全生效
六、查询结果占表数据比例过大
原理:若索引查询结果超过表数据的 30%(经验值),MySQL 认为全表扫描比逐行查索引更高效(索引需多次 I/O,扫描一次完成)。
示例:
-- 表中有 100 万行,status=0 的数据有 50 万行(占 50%)
SELECT * FROM orders WHERE status = 0; -- 即使 status 有索引,也可能全表扫描
七、NOT IN
、!=
、<>
等操作符
原理:这些操作符会导致优化器认为匹配结果分散,索引定位效率低,倾向于全表扫描(视数据分布而定,并非绝对失效)。
示例:
SELECT * FROM users WHERE age != 30; -- 可能失效
SELECT * FROM users WHERE age NOT IN (20, 30); -- 可能失效
八、IS NOT NULL
操作(部分场景)
原理:NULL
在索引中特殊存储,IS NOT NULL
需扫描大部分索引节点,效率接近全表扫描(而 IS NULL
可高效走索引)。
示例:
SELECT * FROM users WHERE email IS NOT NULL; -- 可能失效(若非NULL值占比高)
SELECT * FROM users WHERE email IS NULL; -- 有效(NULL值少,索引定位快)
九、优化器误判(统计信息过时)
原理:MySQL 依赖表统计信息(如数据量、字段分布)选择索引,若统计信息过时(如刚大批量插入数据),可能误判索引效率导致失效。
解决方法:
-- 手动更新统计信息
ANALYZE TABLE 表名;
如何检测索引是否失效?
使用 EXPLAIN
命令分析执行计划,若 type
为 ALL
且 key
为 NULL
,说明索引失效:
EXPLAIN SELECT * FROM users WHERE age + 1 = 30;
总结
索引失效的核心原因是:查询条件破坏了索引的有序性,或优化器认为全表扫描比索引查询更高效。避免失效的关键是:
- 索引字段不参与运算 / 函数;
- 遵循联合索引最左前缀原则;
- 保证查询条件与字段类型一致;
- 避免用
OR
连接非索引字段。
实际开发中,需结合 EXPLAIN
工具和业务数据分布,针对性优化查询语句。