MySQL索引失效全场景解析:这10种坑你踩过几个?

1 阅读4分钟

🚨 MySQL索引失效全场景解析:这10种坑你踩过几个?

#MySQL优化 #索引机制 #数据库调优 #开发避坑指南


一、索引失效的典型症状

问题表现

  • 查询速度突然变慢,但数据量未显著增长
  • EXPLAIN执行计划显示type=ALL(全表扫描)
  • key列为NULL,表示未使用索引

诊断工具

EXPLAIN SELECT * FROM users WHERE phone = '13800138000';  
-- 重点观察以下字段:  
-- type: 访问类型(理想为ref/range)  
-- key: 实际使用的索引  
-- rows: 扫描行数  
-- Extra: 额外信息(如Using where)  

二、10大索引失效场景+解决方案

场景1:对索引列使用函数或表达式

错误示例

SELECT * FROM orders WHERE DATE(create_time) = '2023-08-01';  

失效原因:对索引列create_time使用DATE()函数后,无法使用索引

优化方案

SELECT * FROM orders  
WHERE create_time >= '2023-08-01 00:00:00'  
  AND create_time < '2023-08-02 00:00:00';  

场景2:隐式类型转换

错误示例

SELECT * FROM users WHERE phone = 13800138000; -- phone是varchar类型  

失效原因:数字与字符串比较触发隐式转换,等价于CAST(phone AS signed) = 13800138000

优化方案:保持类型一致

SELECT * FROM users WHERE phone = '13800138000';  

场景3:违背最左前缀原则

索引结构INDEX (col1, col2, col3)

错误示例

SELECT * FROM table WHERE col2 = 'B' AND col3 = 'C';  

失效原因:未使用索引最左列col1,跳过了前缀

优化方案

  1. 调整查询顺序:WHERE col1 = 'A' AND col2 = 'B'
  2. 修改索引设计:INDEX (col2, col3)

场景4:OR条件使用不当

错误示例

SELECT * FROM products  
WHERE category_id = 5 OR price > 100;  

失效原因:若category_idprice各自有索引,但OR会导致全表扫描

优化方案

SELECT * FROM products WHERE category_id = 5  
UNION ALL  
SELECT * FROM products WHERE price > 100;  

场景5:范围查询后的索引列失效

索引结构INDEX (age, name)

错误示例

SELECT * FROM employees  
WHERE age > 30 AND name = '张三';  

失效原因:范围查询age > 30后,后续索引列name无法使用

优化方案:调整索引顺序

ALTER TABLE employees ADD INDEX (name, age);  

场景6:使用不等于(!= 或 <>)

错误示例

SELECT * FROM logs WHERE status != 200;  

失效原因:不等于操作无法有效利用B+树索引结构

优化方案

  1. 改为范围查询:status < 200 OR status > 200
  2. 考虑业务需求是否必要

场景7:LIKE以通配符开头

错误示例

SELECT * FROM articles WHERE title LIKE '%优化%';  

失效原因:前导通配符导致无法使用索引

优化方案

  1. 使用全文索引(FULLTEXT)
  2. 调整查询模式:title LIKE '优化%'

场景8:索引列参与计算

错误示例

SELECT * FROM sales WHERE price + 10 > 100;  

失效原因:索引列price被修改后无法匹配索引

优化方案

SELECT * FROM sales WHERE price > 90;  

场景9:数据分布不均导致优化器弃用索引

案例:某状态字段status(99%为1,1%为0)

SELECT * FROM orders WHERE status = 0; -- 走索引  
SELECT * FROM orders WHERE status = 1; -- 全表扫描  

原因:优化器认为全表扫描效率更高

解决方案:强制使用索引

SELECT * FROM orders FORCE INDEX(idx_status) WHERE status = 1;  

场景10:多表JOIN时的索引失效

错误示例

SELECT * FROM users u  
LEFT JOIN orders o ON u.id = o.user_id  
WHERE o.amount > 1000;  

失效原因:JOIN字段未建立索引

优化方案

ALTER TABLE orders ADD INDEX idx_user_id(user_id);  
ALTER TABLE orders ADD INDEX idx_amount(amount);  

三、索引优化最佳实践

  1. 设计原则

    • 优先选择区分度高的列(如唯一ID)
    • 控制索引数量(一般不超过5个)
    • 联合索引字段不超过5个
  2. 维护策略

    -- 定期分析索引使用情况  
    SELECT * FROM sys.schema_unused_indexes;  
      
    -- 重建碎片化索引  
    ALTER TABLE table_name ENGINE=INNODB;  
    
  3. 监控工具

    • 慢查询日志(slow_query_log)
    • Performance Schema
    • pt-index-usage(Percona Toolkit)

四、实战案例分析

案例背景

用户表users(100万数据),存在以下索引:

INDEX idx_phone (phone),  
INDEX idx_birthday (birthday)  

问题SQL

SELECT * FROM users  
WHERE DATE_FORMAT(birthday, '%Y-%m') = '1990-01'  
  AND phone LIKE '%6666';  

优化步骤

  1. 改写日期条件

    WHERE birthday >= '1990-01-01'  
      AND birthday < '1990-02-01'  
    
  2. 添加覆盖索引

    ALTER TABLE users ADD INDEX idx_phone_birthday(phone, birthday);  
    

优化效果对比

指标优化前优化后
执行时间2.3秒0.05秒
扫描行数985,4321,024

五、总结与避坑指南

必记口诀

  • 函数计算要避免
  • 最左前缀不能断
  • LIKE百分右边放
  • 类型一致是关键
  1. 终极建议

    1. 所有SQL上线前必须用EXPLAIN验证执行计划
    2. 使用FORCE INDEX需谨慎,优先优化索引设计
    3. 定期使用ANALYZE TABLE更新统计信息

    📢 互动话题:你在项目中遇到过哪些奇葩的索引失效案例?欢迎评论区分享讨论!💬 🔗 资源推荐MySQL官方索引文档