轉載
MySQL在 5.0版本中引入新特性:索引合並優化(Index merge optimization),當查詢中單張表可以使用多個索引時,同時掃描多個索引並將掃描結果進行合並。
該特新主要應用於以下三種場景:
1、 對OR語句求並集,如查詢SELECT * FROM TB1 WHERE c1="xxx" OR c2=""xxx"時,如果c1和c2列上分別有索引,可以按照c1和c2條件進行查詢,再將查詢結果合並(union)操作,得到最終結果
2、 對AND語句求交集,如查詢SELECT * FROM TB1 WHERE c1="xxx" AND c2=""xxx"時,如果c1和c2列上分別有索引,可以按照c1和c2條件進行查詢,再將查詢結果取交集(intersect)操作,得到最終結果
3、 對AND和OR組合語句求結果
該新特性可以在一些場景中大幅度提升查詢性能,但受限於MySQL糟糕的統計信息,也導致很多場景查詢性能極差甚至導致數據庫崩潰。
以SELECT * FROM TB1 WHERE c1="xxx" AND c2=""xxx" 為例:
1、 當c1列和c2列選擇性較高時,按照c1和c2條件進行查詢性能較高且返回數據集較小,再對兩個數據量較小的數據集求交集的操作成本也較低,最終整個語句查詢高效;
2、 當c1列或c2列選擇性較差且統計信息不准時,比如整表數據量2000萬,按照c2列條件返回1500萬數據,按照c1列返回1000條數據,此時按照c2列條件進行索引掃描+聚集索引查找的操作成本極高(可能是整表掃描的百倍消耗),對1000條數據和1500萬數據求交集的成本也極高,最終導致整條SQL需要消耗大量CPU和IO資源且相應時間超長,而如果值使用c1列的索引,查詢消耗資源較少且性能較高。
由於上述的問題,絕大多數的運維團隊都會選擇關閉該特性來避免執行異常,京東商城也出現過類似案例,嚴重影響業務正常運行。
最近系統中發現SQL執行異常,SQL類似為:
SELECT *
FROM tb_xxxx_xxxx
WHERE yn=0
AND C1=‘123456789’
OR C2=‘123456789’;
表上C1和C2列分別建有索引,但OR條件導致僅掃描任何一個索引都無法得到滿足條件的全部數據,需要同時掃描兩個索引並對兩個臨時結果求並集,但由於我們關閉了Index merge特性,導致執行優化器只能對表進行全表掃描並導致執行性能不佳。
該問題的臨時解決辦法為開啟Index merge特性,但存在未知風險,因此我們建議修改SQL,將OR操作修改為UNION操作,使得不開啟Index merge特性的情況下語句依然能使用多個索引,優化SQL為:
SELECT *
FROM tb_xxxx_xxxx
WHERE yn=0
AND C1=‘123456789’
UNION ALL
SELECT *
FROM tb_xxxx_xxxx
WHERE yn=0
AND C2=‘123456789’
AND C1<>‘123456789’
PS:
1、在第二個SELECT語句中增加第一個SELECT語句條件的反操作,從而保證兩個SELECT 語句中沒有重復數據,可以使用UNION ALL來求交集,避免UNION所帶來的排序消耗。
2、在編寫SQL語句時,需要注意OR條件的書寫,
原SQL為:
WHERE yn=0
AND C1=‘123456789’
OR C2=‘123456789’
等價於:
WHERE (yn=0 AND C1=‘123456789’)
OR C2=‘123456789’
而實際需求要求所有返回數據滿足yn=0的條件,應正確寫為:
WHERE yn=0
AND (C1=‘123456789’
OR C2=‘123456789’)