1. MySQL索引失效的典型场景剖析
索引是MySQL性能优化的核心手段,但实际工作中我们常遇到"明明加了索引却不见效"的情况。根据我多年处理生产环境性能问题的经验,索引失效往往由以下七种典型场景引发:
1.1 最左前缀原则违反
联合索引遵循最左匹配原则,比如有索引idx_name_age(name, age):
sql复制-- 有效查询(使用索引)
SELECT * FROM users WHERE name = '张三' AND age = 25;
SELECT * FROM users WHERE name LIKE '张%';
-- 失效查询(跳过name直接查age)
SELECT * FROM users WHERE age = 25;
原理说明:B+树索引的存储结构决定了必须从左到右匹配。就像查字典时不能直接根据"第二字母"查找。
1.2 隐式类型转换陷阱
当字段类型与查询条件类型不匹配时:
sql复制-- 假设mobile字段是varchar类型
SELECT * FROM users WHERE mobile = 13800138000; -- 失效
SELECT * FROM users WHERE mobile = '13800138000'; -- 有效
常见踩坑点:
- 字符串字段与数字比较
- 日期字段与字符串比较
- ENUM类型与数值比较
1.3 索引列参与运算
任何对索引列的运算都会导致失效:
sql复制-- 失效操作
SELECT * FROM orders WHERE YEAR(create_time) = 2023;
SELECT * FROM products WHERE price + 100 > 500;
-- 优化方案
SELECT * FROM orders WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
SELECT * FROM products WHERE price > 400;
1.4 OR条件使用不当
OR条件可能导致全表扫描:
sql复制-- 联合索引idx_a_b(a,b)
SELECT * FROM table WHERE a = 1 OR b = 2; -- 失效
-- 优化方案1:改用UNION
SELECT * FROM table WHERE a = 1
UNION
SELECT * FROM table WHERE b = 2;
-- 优化方案2:使用覆盖索引
SELECT a,b FROM table WHERE a = 1 OR b = 2;
1.5 模糊查询滥用通配符
LIKE语句的通配符位置影响索引使用:
sql复制-- 有效查询
SELECT * FROM articles WHERE title LIKE 'MySQL%';
-- 失效查询
SELECT * FROM articles WHERE title LIKE '%索引%';
SELECT * FROM articles WHERE title LIKE '%失效';
1.6 索引选择性过低
当索引列重复值过多时,优化器可能放弃使用索引:
sql复制-- gender字段只有'M'/'F'两种值
SELECT * FROM users WHERE gender = 'M'; -- 可能全表扫描
判断标准:索引选择性 = 不重复值数量/总记录数,低于10%时需谨慎。
1.7 优化器误判
统计信息不准确可能导致优化器错误选择:
sql复制-- 强制使用索引
SELECT * FROM table FORCE INDEX(idx_name) WHERE name = 'test';
更新统计信息命令:
sql复制ANALYZE TABLE table_name;
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 索引失效的诊断方法论
2.1 EXPLAIN执行计划解析
关键字段解读:
code复制+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
重点关注:
- type:ALL表示全表扫描
- key:NULL表示未使用索引
- Extra:Using filesort/Using temporary需要优化
2.2 性能监控工具
推荐工具组合:
- 慢查询日志
sql复制-- 开启配置
slow_query_log = ON
long_query_time = 1
log_queries_not_using_indexes = ON
- Performance Schema
sql复制-- 查看未使用索引的查询
SELECT * FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT LIKE '%SELECT%' AND SUM_NO_INDEX_USED > 0;
2.3 索引使用情况统计
查看索引使用频率:
sql复制SELECT object_schema, object_name, index_name, count_read, count_fetch
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
ORDER BY count_read DESC;
3. 高级优化策略与实践
3.1 索引跳跃扫描(MySQL 8.0+)
对于复合索引(a,b,c),8.0+版本支持:
sql复制-- 即使缺少a条件也能使用索引
SELECT * FROM table WHERE b = 2 AND c = 3;
3.2 函数索引(MySQL 8.0+)
创建函数索引解决运算导致的失效:
sql复制-- 创建函数索引
CREATE INDEX idx_year ON orders((YEAR(create_time)));
-- 查询使用
SELECT * FROM orders WHERE YEAR(create_time) = 2023;
3.3 索引合并优化
优化器自动合并多个单列索引:
sql复制-- 有索引idx_a(a)和idx_b(b)
SELECT * FROM table WHERE a = 1 OR b = 2;
可通过参数控制:
sql复制optimizer_switch='index_merge=on,index_merge_union=on'
3.4 覆盖索引优化
只需从索引获取数据时效率最高:
sql复制-- 创建覆盖索引
ALTER TABLE orders ADD INDEX idx_cover(user_id,status,create_time);
-- 查询只需索引列
SELECT user_id, status FROM orders WHERE user_id = 1001;
4. 生产环境避坑指南
4.1 索引设计黄金法则
- 单表索引不超过5个
- 联合索引字段数不超过3个
- 区分度高的字段放前面
- 避免冗余索引(如已有(a,b)就不需要单独的a索引)
4.2 高频踩坑场景
- 使用NOT IN或<>操作符
sql复制-- 全表扫描
SELECT * FROM users WHERE status NOT IN (1,2);
- IS NULL判断
sql复制-- 可能失效
SELECT * FROM users WHERE phone IS NULL;
- 多范围查询
sql复制-- 只能用到age索引
SELECT * FROM users WHERE age > 20 AND age < 30 AND salary > 5000;
4.3 索引维护最佳实践
- 定期检查无用索引
sql复制SELECT * FROM sys.schema_unused_indexes;
- 碎片整理
sql复制ALTER TABLE table_name ENGINE=InnoDB;
- 监控索引大小
sql复制SELECT table_name, index_name, stat_value*@@innodb_page_size/1024/1024 AS size_mb
FROM mysql.innodb_index_stats
WHERE stat_name = 'size';
5. 真实案例诊断实录
5.1 电商订单查询优化
原始SQL:
sql复制SELECT * FROM orders
WHERE DATE_FORMAT(create_time,'%Y-%m') = '2023-06'
AND status = 2
ORDER BY amount DESC;
优化方案:
- 创建函数索引
sql复制CREATE INDEX idx_ym_status ON orders((DATE_FORMAT(create_time,'%Y-%m')), status);
- 改写查询条件
sql复制SELECT * FROM orders
WHERE create_time BETWEEN '2023-06-01' AND '2023-06-30'
AND status = 2
ORDER BY amount DESC;
5.2 用户画像标签查询
原始SQL:
sql复制SELECT user_id FROM user_tags
WHERE tag_id IN (101,205,307)
GROUP BY user_id
HAVING COUNT(DISTINCT tag_id) = 3;
优化方案:
- 使用覆盖索引
sql复制ALTER TABLE user_tags ADD INDEX idx_cover(tag_id,user_id);
- 使用JOIN替代IN
sql复制SELECT t1.user_id
FROM user_tags t1
JOIN user_tags t2 ON t1.user_id = t2.user_id AND t2.tag_id = 205
JOIN user_tags t3 ON t1.user_id = t3.user_id AND t3.tag_id = 307
WHERE t1.tag_id = 101;
6. 索引优化检查清单
在每次SQL优化时,建议按此清单检查:
- [ ] EXPLAIN确认执行计划
- [ ] 检查WHERE条件字段是否有合适索引
- [ ] 避免对索引列进行运算
- [ ] 模糊查询是否前导通配符
- [ ] OR条件是否可改写为UNION
- [ ] 是否使用了覆盖索引
- [ ] 复合索引是否满足最左前缀
- [ ] 字段类型是否匹配
- [ ] 索引选择性是否足够高
- [ ] 是否出现Using filesort/Using temporary
实际工作中发现,80%的性能问题通过检查前3项就能定位。对于关键业务SQL,建议在测试环境用真实数据量验证索引效果。
