1. 索引失效现象解析
数据库索引就像图书馆的目录系统,设计得当能极大提升查询效率。但在实际工作中,我们常会遇到"明明建立了索引,查询却依然缓慢"的情况。上周排查一个生产环境慢查询时,发现一个精心设计的复合索引完全没被使用,导致原本应该毫秒级响应的接口变成了5秒以上的超时请求。
索引失效的本质是数据库优化器认为"使用索引反而比全表扫描更慢"。这种判断可能源于查询语句的编写方式、表结构设计或数据分布特征。根据我处理过的300+个性能案例,80%的索引失效问题集中在五个典型场景中。
2. 五大关键失效场景详解
2.1 隐式类型转换陷阱
当查询条件的数据类型与索引列定义不一致时,MySQL会进行隐式类型转换。例如:
sql复制-- user_id是varchar类型但用数字查询
SELECT * FROM users WHERE user_id = 10086;
这种情况下,优化器会对索引列使用CAST函数,导致索引失效。我曾遇到一个订单系统因此产生全表扫描,800万数据表查询耗时从2ms暴涨到1200ms。
解决方案:
- 使用
SHOW WARNINGS查看类型转换提示 - 严格匹配字段类型,字符串加引号
- 在应用层统一类型处理
2.2 最左前缀原则违反
复合索引遵循"最左匹配"原则,像电话簿按"姓+名"排序后,单查"名"就无法利用索引。典型错误:
sql复制-- 有索引(idx_type_status)
SELECT * FROM orders WHERE status = 'paid';
避坑指南:
- 高频查询条件必须放在复合索引左侧
- 使用
EXPLAIN确认索引使用情况 - 必要时建立冗余单列索引
2.3 索引列参与运算
在索引列上使用函数或运算会使索引失效:
sql复制-- 不会使用create_time的索引
SELECT * FROM logs WHERE YEAR(create_time) = 2023;
优化方案:
- 改为范围查询:
WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31' - 使用生成列存储运算结果并建索引
- 对于JSON字段使用
->>操作符而非JSON函数
2.4 范围查询阻断后续索引
范围查询(>、<、BETWEEN)会阻断复合索引中后续列的匹配:
sql复制-- 复合索引(date, user_id)
SELECT * FROM events
WHERE date > '2023-01-01'
AND user_id = 10086; -- user_id无法使用索引
设计建议:
- 将等值查询列放在复合索引左侧
- 范围查询列尽量靠右
- 考虑使用分区表替代大范围查询
2.5 选择性不足的索引
当索引列不同值很少时(如性别、状态标志),优化器会放弃使用索引:
sql复制-- status只有0/1两种值
SELECT * FROM products WHERE status = 1;
处理策略:
- 组合低选择性列与高选择性列建复合索引
- 使用
FORCE INDEX强制使用索引(需谨慎) - 考虑使用位图索引(如MySQL 8.0的不可见索引)
3. 诊断与优化实战
3.1 索引使用分析工具链
-
EXPLAIN:重点关注type列(ALL表示全表扫描)、key列(实际使用的索引)
sql复制EXPLAIN SELECT * FROM users WHERE phone='13800138000'; -
性能模式:MySQL 5.7+的
sys.schema_index_statistics视图sql复制SELECT * FROM sys.schema_index_statistics WHERE table_schema='your_db'; -
慢查询日志:配合pt-query-digest分析
bash复制
pt-query-digest /var/log/mysql/mysql-slow.log
3.2 索引优化黄金法则
-
三星索引原则:
- 一星:WHERE条件匹配索引最左前缀
- 二星:ORDER BY子句匹配索引顺序
- 三星:SELECT列被索引覆盖
-
索引维护策略:
- 定期使用
ANALYZE TABLE更新统计信息 - 删除冗余索引(工具:pt-index-usage)
- 监控索引碎片率(
SHOW TABLE STATUS)
- 定期使用
-
特殊场景处理:
- 分页查询优化:
WHERE id > last_id LIMIT 100 - 模糊查询:右匹配可用索引(
LIKE 'prefix%') - JSON字段:对常用路径建立函数索引
- 分页查询优化:
4. 生产环境案例复盘
去年优化过一个电商平台的订单查询接口,原始查询:
sql复制SELECT * FROM orders
WHERE DATE_FORMAT(pay_time,'%Y-%m')='2023-06'
AND user_id IN (1001,1002,1003)
ORDER BY amount DESC;
问题诊断:
- DATE_FORMAT导致pay_time索引失效
- IN子查询优化不佳
- 排序字段无索引
优化方案:
- 建立复合索引
(user_id, pay_time, amount) - 改写日期查询为范围:
sql复制WHERE pay_time >= '2023-06-01' AND pay_time < '2023-07-01' - 使用UNION替代IN查询
优化后查询耗时从1.8s降至23ms,QPS提升80倍。这个案例充分展示了正确理解索引失效场景的价值。