一次奇怪的mysql查詢優化發現mysql查詢優化器的bug

        最近剛上線的一個sql出現了慢查,經過分析發現了一個很有意思的問題,具體表現爲,select * from tablename where a=xx and b=xx and c=xxx and time<xxx order by time desc; 這條sql的查詢很快的,在60ms以內,但是如果再最後加上了limit 10,查詢就會要2s多。當這個慢查被運維拉出來的時候,感覺非常不能理解。

        我先把表結構儘量簡化一下。

CREATE TABLE `tablename` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT 
  `typeId` tinyint(1) unsigned NOT NULL DEFAULT '0' 
  `userId` int(11) unsigned NOT NULL COMMENT '用戶id',
  `orderId` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '訂單id',
  `tradeStatus` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '交易狀態 0.交易中 1.交易關閉 2.交易成功',
  `otherStatus` tinyint(1) unsigned NOT NULL DEFAULT '0' ,
  `addTime` int(10) unsigned NOT NULL COMMENT '添加時間',
  PRIMARY KEY (`id`),
  KEY `IDX_tradeStatus_otherStatus_addTime` (`tradeStatus`,`otherStatus`,`addTime`) USING BTREE,
  KEY `IDX_tradeStatus_otherStatus_typeId_orderId` (`tradeStatus`,`otherStatus`,`typeId`,`orderId`),
  KEY `IDX_addTime` (`addTime`),
  KEY `IDX_userId_tradeStatus_addTime` (`userId`,`tradeStatus`,`addTime`) USING BTREE,
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='test'

        我最初的查詢sql是

select *
  from tablename
 where userid= 123
   and tradestatus= 2
   and orderid= 0
   and addTime< 1566139866
order by addtime desc limit 10

        看了一下執行計劃,當沒有limit的時候,執行計劃走了最後一條索引,執行計劃的type是ref,rows是22800。當加上limit以後,執行計劃只走了addTime的索引,並且type退化成了range,rows幾乎是全表了。

        這是一個很詭異的事情,研究了幾分鐘後發現搞不定,決定先優化它,再研究問題。然後發現了兩種解決方案。

方案一,將查詢語句和limit語句分隔開:

select *
  from(
select *
  from tablename
 where userid= 3307619
   and tradestatus= 2
   and orderid= 0
   and addTime< 1566139866
order by addtime desc) tab limit 10

方案二,使用強制索引語句:

select *
  from tablename force index(IDX_userId_tradeStatus_addTime)
 where userid= 123
   and tradestatus= 2
   and orderid= 0
   and addTime< 1566139866
order by addtime desc limit 10

        處理完後發現這兩個方案其實都是在強行使用了mysql更好的索引,並且第一個解決方案因爲避開了limit反而使用了正確的索引,所以我懷疑有可能是mysql的查詢優化器在遇到limit以後會失效。帶着這個疑問又去查詢資料,終於被我找到一篇帖子。

        https://yq.aliyun.com/articles/51065/

        這裏把bug產生的原因貼出來,具體的內容大家可以自己去查看。

  • 第一階段,優化器選擇了索引 iabc,採用 range 訪問;
  • 第二階段,優化器試圖進一步優化執行計劃,使用 order by 的列訪問,並清空了第一階段的結果;
  • 第三階段,優化器發現使用 order by 的列訪問,代價比第一階段的結果更大,但是第一階段結果已經被清空了,無法還原,於是選擇了代價較大的訪問方式(index_scan),觸發了bug。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章