MySQL 慢查询排查实录：一次优化器误判引发的性能危机

一、问题背景

某线上接口响应时间高达 50 秒以上，频繁超时，严重影响用户体验。测试环境却运行流畅，引发了排查行动。

二、初步排查过程

✅ 1. 链路追踪识别慢接口

通过链路追踪系统（如 SkyWalking）快速定位到是某数据库查询拖慢了接口响应。

✅ 2. 日志 + SQL 拦截器打印完整 SQL

结合 MyBatis 的 SQL 拦截器，拿到完整参数拼接后的 SQL：

Copy
SELECT MAX(t.id)
FROM user_log t
WHERE t.deleted_at = '1970-01-01 00:00:00'
AND LENGTH(t.platform_id) > 0
AND t.platform_id != '0'
AND t.platform_type = 'MOBILE'
AND t.nickname LIKE CONCAT('%', 'abc', '%')
GROUP BY t.platform_id
LIMIT 20;

✅ 3. 执行计划分析（EXPLAIN）

发现生产环境错误命中了一个 deleted_at 开头的索引：

Copy
idx_deletedat_memberaccountid_updatedat
→ 扫描行数：2,126,736
→ filesort + 临时表
→ 总耗时：50秒

而测试环境使用了更合适的索引 idx_platformtype_deletedat_platformid_nickname，执行仅需数百毫秒。

三、问题根因

优化器在生产环境中误判了最优索引，导致全表扫描 + 临时表排序。

原因如下：

条件字段 | 测试环境匹配量级 | 生产环境匹配量级
deleted_at | 千级（约 5,000） | 百万级（约 2,100,000）
platform_type | 十万级（约 700,000） | 百万级（约 1,250,000）

💡推断结果：生产环境中 deleted_at 匹配数据过多，导致优化器错误判断为过滤性强字段，选择了 sub-optimal 的索引。

四、最终优化方案：组合两种策略

✅ 方案一：条件性使用 USE INDEX

MyBatis XML 中动态注入：

Copy
<if test="ro.platformType != null and ro.partialNickname != null and ro.talentPlatformIdList == null">
USE INDEX (idx_platformtype_deletedat_platformid_nickname)
</if>

作用：
・ 避免在昵称列表查询、没有 platform_id 精确范围时优化器选错；
・ 限制性注入，确保只在特定组合下才强制索引，避免一刀切。

✅ 方案二：范围优化替换函数过滤

替换原本的冗余判断逻辑：

-- 原来

Copy
AND LENGTH(t.platform_id) > 0 AND t.platform_id != '0'

-- 优化后

Copy
AND t.platform_id > '0'

优势：

length 和！= 会用不上索引，直接扫表，改为 > 可以使用索引进行查询

避免函数对字段的干扰，提升索引使用概率；

简洁高效，语义等效。

五、优化效果

环境 | 优化前 | 优化后 | 提升倍数
生产环境 | ~50 秒 | ~0.9 秒 | 55x

六、后续优化

以下是在当前方案基础上，总结出的更进一步的可选优化方向，适合后续演进考虑，目前先不继续优化，因为下面的 ROI 太低。

✅ 使用全文索引优化模糊搜索

当前 LIKE '% xxx%' 查询无法使用 B-Tree 索引。

可使用 FULLTEXT 索引（适用于 InnoDB 中 VARCHAR 或 TEXT 字段），提高模糊搜索性能：

Copy
ALTER TABLE some_table ADD FULLTEXT INDEX idx_nickname_ft (nickname);

查询改写为：

Copy
SELECT ... FROM some_table
WHERE MATCH(nickname) AGAINST('+abc' IN BOOLEAN MODE);

✅ SQL 拆分处理 OR 条件

原查询中存在：

Copy
WHERE platform_id IN (...) OR home_link IN (...)

MySQL 遇到 OR 时，通常无法同时利用两个索引，容易触发全表扫描。

推荐将 SQL 拆成两条子查询后 UNION ALL：

Copy
(SELECT ... FROM ... WHERE platform_id IN (...))
UNION ALL
(SELECT ... FROM ... WHERE home_link IN (...))

✅ 设计多组复合索引应对不同参数组合

针对不同查询场景（如是否含 platform_type、是否模糊搜索、是否有 notification_id），分别设计专用复合索引；

并用 MyBatis 条件 + USE INDEX 动态控制路径，确保始终走最优执行计划。

✅ 通过直方图提升优化器对数据分布的理解（MySQL 8+）

若 ANALYZE TABLE 仍不足以准确反映某字段基数分布，可考虑使用：

Copy
ANALYZE TABLE some_table UPDATE HISTOGRAM ON platform_type WITH 256 BUCKETS;

提升优化器对索引代价评估的准确性。

七、总结与启示

🎯 优化关键词：
・ USE INDEX 条件控制
・ 函数过滤改范围判断
・ 分析数据分布差异
・ 关注优化器行为
・ 精准条件下强引导，避免全局强绑

🧠 经验总结：

建议 | 说明
优化前务必 EXPLAIN 对比 | 理解生产 vs 测试为何差异巨大
慎用函数包裹索引字段 | 函数过滤常导致索引失效
条件性 USE INDEX 而非全局固定 | 提高兼容性与鲁棒性
字段分布决定优化器行为 | 不同环境需分别调优

⸻

一次错误的索引选择，能将查询放大 50 倍耗时；一次精细的微调组合，就能让系统重获新生。

面对优化器，别 “全信”，也别 “硬刚”，用更细致的策略去协同它，才是高阶的 SQL 优化之道。