MySQL 全索引掃描的bug

來源:互聯網
上載者:User

MySQL 全索引掃描的bug

一 簡介

在檢查某業務資料庫的slowlog 時發現一個慢查詢,查詢時間 1.57s ,檢查表結構 where條件欄位存在正確的複合式索引,正確的情況下最佳化器應該選擇複合式索引,而非為啥會導致慢查詢呢? 且看本文慢慢分析。

二 分析

案例中的MySQL資料庫版本 5.6.16 將生產環境的sql做適當修改,where條件不變。讀者朋友可以測試一下其他的版本。

root@rac1 10:48:11>explain select id,
 
    -> gmt_create,

    -> gmt_modified,

    -> order_id,

    -> service_id,

    -> seller_id,

    -> seller_nick,

    -> sale_type

    -> from lol

    -> where seller_id= 1501204

    -> and service_id= 1

    -> and sale_type in(3, 4)

    -> and use_status in(3, 4, 5, 6)

    -> and process_node_id= 6 order by id desc limit 0,20 \G

*************************** 1. row ***************************

          id: 1

  select_type: SIMPLE

        table: lol

        type: index

possible_keys: idx_sellerid,idx_usestatus_saletype,idx_sellerid_saletype,idx_sidustsvidtype

          key: PRIMARY

      key_len: 8

          ref: NULL

        rows: 3076

        Extra: Using where

1 row in set (0.00 sec)

分析

MySQL選擇的執行計畫利用主鍵進行訪問資料。注意執行計畫中的 access type是index,而index 意味著這個SQL在查詢二級索引的時候,對二級索引進行了全索引掃描,根本沒有進行過濾

這個行為是不合理的,因為where條件中含有 in 查詢,合理的執行計畫的access type應該是range。

我們採用強制索引,看看結果

root@rac1 10:48:07>explain select id,
 
    -> gmt_create,

    -> gmt_modified,

    -> order_id,

    -> service_id,

    -> seller_id,

    -> seller_nick,

    -> sale_type

    -> from lol force index(idx_sidustsvidtype)

    -> where seller_id= 1501204

    -> and service_id= 1

    -> and sale_type in(3, 4)

    -> and use_status in(3, 4, 5, 6)

    -> and process_node_id= 6 order by id desc limit 0,20 \G

*************************** 1. row ***************************

          id: 1

  select_type: SIMPLE

        table: lol

        type: range

possible_keys: idx_sidustsvidtype

          key: idx_sidustsvidtype

      key_len: 19

          ref: NULL

        rows: 5178

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

分析

強制加上索引之後的執行計畫是符合預期的,執行sql的時間由 1.57s 減少為 0.01s 。因此我們推測是在最佳化器選擇索引的時候出現了問題

結合源碼和optimize_trace我們發現第一階段最佳化的時候,最佳化器確實選擇了idx_sidustsvidtype 並且選擇採用range訪問,因為sql 語句中含有order by,在optimizer試圖最佳化 order by limit的時候

清空了儲存訪問方式的quick變數(原本儲存的是range,但是被請空),最終發現採用排序索引(這裡是id)的代價高於複合式索引(這裡是idx_sidustsvidtype)時,還是選擇了idx_sidustsvidtype

但是悲劇的是這時候正確的訪問方式已經被清空,無法還原,這就是這個 bug#78993  的根本成因。

根據分析,我們還可以使用另一種解決方案----去掉 order by 。當然這個對業務所有入侵必須和開發溝通確認sql的結果集是否唯一,如果不唯一還是要使用其他方法。

root@rac1 10:48:15>explain select id,
 
    -> gmt_create,

    -> gmt_modified,

    -> order_id,

    -> service_id,

    -> seller_id,

    -> seller_nick,

    -> sale_type

    -> from lol

    -> where seller_id= 1501204

    -> and service_id= 1

    -> and sale_type in(3, 4)

    -> and use_status in(3, 4, 5, 6)

    -> and process_node_id= 6 \G

*************************** 1. row ***************************

          id: 1

  select_type: SIMPLE

        table: lol

        type: range

possible_keys: idx_sellerid,idx_uts_stp,idx_sid_stpe,idx_sidustsvidtype

          key: idx_sidustsvidtype

      key_len: 19

          ref: NULL

        rows: 5178

        Extra: Using where

1 row in set (0.00 sec)

三 總結
a 修改SQL,添加正確hint。
b 去掉不必要的order by 需要和開發溝通確認是否影響商務邏輯。
c 修改最佳化的bug,保留多個訪問路徑,不清理儲存訪問方式的quick變數,發現orderby 的代價高於複合式索引時,可以選擇最優的訪問路徑。

特別感謝 江疑 的分析,同時也推薦《排序sql升級MySQL 5.6變慢原因分析》 

本文永久更新連結地址:

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.