simon

simon

github

MySQL 慢查詢排查實錄:一次優化器誤判引發的性能危機

一、問題背景#

某線上接口響應時間高達 50 秒以上,頻繁超時,嚴重影響用戶體驗。測試環境卻運行流暢,引發了排查行動。

二、初步排查過程#

✅ 1. 鏈路追蹤識別慢接口

透過鏈路追蹤系統(如 SkyWalking)快速定位到是某資料庫查詢拖慢了接口響應。

✅ 2. 日誌 + SQL 攔截器打印完整 SQL

結合 MyBatis 的 SQL 攔截器,拿到完整參數拼接後的 SQL:

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 開頭的索引:

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 中動態注入:

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

作用:
・ 避免在暱稱列表查詢、沒有 platform_id 精確範圍時優化器選錯;
・ 限制性注入,確保只在特定組合下才強制索引,避免一刀切。

✅ 方案二:範圍優化替換函數過濾

替換原本的冗餘判斷邏輯:

-- 原來

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

-- 優化後

AND t.platform_id > '0'

優勢:

  • length 和!= 會用不上索引,直接掃表,改為 > 可以使用索引進行查詢
  • 避免函數對字段的干擾,提升索引使用概率;
  • 簡潔高效,語義等效。

五、優化效果#

環境優化前優化後提升倍數
生產環境~50 秒~0.9 秒55x

六、後續優化#

以下是在當前方案基礎上,總結出的更進一步的可選優化方向,適合後續演進考慮,目前先不繼續優化,因為下面的 ROI 太低。

  1. ✅ 使用全文索引優化模糊搜索
  • 當前 LIKE '% xxx%' 查詢無法使用 B-Tree 索引。
  • 可使用 FULLTEXT 索引(適用於 InnoDB 中 VARCHAR 或 TEXT 字段),提高模糊搜索性能:
ALTER TABLE some_table ADD FULLTEXT INDEX idx_nickname_ft (nickname);
  • 查詢改寫為:
SELECT ... FROM some_table
WHERE MATCH(nickname) AGAINST('+abc' IN BOOLEAN MODE);
  1. ✅ SQL 拆分處理 OR 條件
  • 原查詢中存在:
WHERE platform_id IN (...) OR home_link IN (...)
  • MySQL 遇到 OR 時,通常無法同時利用兩個索引,容易觸發全表掃描。
  • 推薦將 SQL 拆成兩條子查詢後 UNION ALL:
(SELECT ... FROM ... WHERE platform_id IN (...))
UNION ALL
(SELECT ... FROM ... WHERE home_link IN (...))
  1. ✅ 設計多組複合索引應對不同參數組合
  • 針對不同查詢場景(如是否含 platform_type、是否模糊搜索、是否有 notification_id),分別設計專用複合索引;
  • 並用 MyBatis 條件 + USE INDEX 動態控制路徑,確保始終走最優執行計劃。
  1. ✅ 透過直方圖提升優化器對數據分佈的理解(MySQL 8+)
  • 若 ANALYZE TABLE 仍不足以準確反映某字段基數分佈,可考慮使用:
ANALYZE TABLE some_table UPDATE HISTOGRAM ON platform_type WITH 256 BUCKETS;
  • 提升優化器對索引代價評估的準確性。

七、總結與啟示#

🎯 優化關鍵詞:
・ USE INDEX 條件控制
・ 函數過濾改範圍判斷
・ 分析數據分佈差異
・ 關注優化器行為
・ 精準條件下強引導,避免全局強綁

🧠 經驗總結:

建議說明
優化前務必 EXPLAIN 對比理解生產 vs 測試為何差異巨大
慎用函數包裹索引字段函數過濾常導致索引失效
條件性 USE INDEX 而非全局固定提高兼容性與魯棒性
字段分佈決定優化器行為不同環境需分別調優

一次錯誤的索引選擇,能將查詢放大 50 倍耗時;一次精細的微調組合,就能讓系統重獲新生。

面對優化器,別 “全信”,也別 “硬剛”,用更細緻的策略去協同它,才是高階的 SQL 優化之道。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。