MySQL的一次優化記錄 (IN子查詢和索引優化)


這兩天實習項目遇到一個網頁加載巨慢的問題(10多秒),然后定位到是一個MySQL查詢特別慢的語句引起的:

SELECT *
FROM (
	SELECT DISTINCT t.vc_date, t.c_bankno, t.vc_bankacco, t.vc_moneytype, t.en_totalbala
		, t.en_usablebala, t1.vc_nameinbank, date_format(t.D_IMPORTTIME, '%Y-%m-%d %H:%i:%S') AS D_IMPORTTIME
		, t.vc_fundcode, t.c_datamode, t.vc_taskid, t.id, t.vc_projectname
		, t.vc_projectcode, t1.c_accotype
		, (
			SELECT IF(vc_occurtime IS NULL, DATE_FORMAT(vc_occurdate, '%Y-%m-%d'), DATE_FORMAT(CONCAT(vc_occurdate, vc_occurtime), '%Y-%m-%d %H:%i:%S')) AS tradeTime
			FROM tbanktradedetail_view
			WHERE vc_bankacco = t.vc_bankacco
		) AS d_tradetime, t3.vc_entry_caption AS C_ACCOTYPE_STR, t5.vc_entry_caption AS VC_BANKNAME, t4.vc_entry_caption AS VC_MONEYTYPE_STR
	FROM tbankaccobala t
		INNER JOIN tbankaccoinfo t1 ON t.vc_bankacco = t1.vc_bankacco
		INNER JOIN (
			SELECT vc_entry_value, vc_entry_caption
			FROM ot_dic_tdictionaryentry
			WHERE vc_entry_no = '5087'
		) t3
		ON t1.c_accotype = t3.vc_entry_value
		INNER JOIN (
			SELECT vc_entry_value, vc_entry_caption
			FROM ot_dic_tdictionaryentry
			WHERE vc_entry_no = '1004'
		) t4
		ON t1.VC_MONEYTYPE = t4.vc_entry_value
		INNER JOIN (
			SELECT vc_entry_value, vc_entry_caption
			FROM ot_dic_tdictionaryentry
			WHERE vc_entry_no = '1014'
		) t5
		ON t.c_bankno = t5.vc_entry_value
	WHERE 1 = 1
		AND t.id IN (
            --   this query will take 4.6s:
			-- SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC), ',', 1)
			-- FROM tbankaccobala
			-- GROUP BY vc_bankacco
            -- but the following query only takes 1.1s:
            SELECT hhhh from(
                SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC), ',', 1) as hhhh
                FROM tbankaccobala
                GROUP BY vc_bankacco
            ) as sbstr
            -- 對IN的子查詢做二次查詢
		)
) t
WHERE 1 = 1
ORDER BY t.D_IMPORTTIME DESC

抽出查詢慢關鍵部分:

SELECT *
FROM (
	SELECT DISTINCT t.vc_date, t.c_bankno, t.vc_bankacco, t.vc_moneytype, t.en_totalbala
        -- 此處省略選擇多個列語句
	FROM tbankaccobala t
        -- 此處省略多張表連表查詢語句
	WHERE 1 = 1
		AND t.id IN (
            --   這個查詢需要3s:
			SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC), ',', 1)
			FROM tbankaccobala
			GROUP BY vc_bankacco
		)
) t

這個語句導致前端頁面10多秒才有響應(但MySQL執行顯示要4.6秒,phpMyAdmin也是10秒左右響應,為何?)

IN子查詢語句優化

把IN語句里面的內容改成下面這樣,只在外層再加一個select,就把3s的查詢縮短為0.006s:

            SELECT hhhh from(
                SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC), ',', 1) as hhhh
                FROM tbankaccobala
                GROUP BY vc_bankacco
            ) as sbstr
            -- 對IN的子查詢做二次select,或者把IN改為JOIN都可以解決速度奇慢的問題

原語句空行處省略了一系列的其他表和 INNER JOIN 語句。一開始懷疑是多表的JOIN操作導致速度變慢,但刪去JOIN變成上面這段注釋掉的語句之后,速度依然非常慢,顯示要3s,於是猜測 IN 才是導致速度變慢的主要因素,改后只要0.006s,嘖…

EXPLAIN 未優化的語句:

(相關子查詢是使用外部查詢中的值的子查詢)

EXPLAIN 優化的語句:

我的理解:優化前,子查詢是相關子查詢,對於外部產生的每個值,都要執行一次子查詢;優化后,子查詢不再是相關子查詢,只需要執行一次子查詢並緩存中間結果,外部查到的每個值去緩存的中間結果里比對一下就行了。

(有人說是能不能用索引的原因——這么說應該是不對的)

完整查詢的后端響應速度對比:
前:

后:

索引優化

對於這么小的數據規模,時間還是太長了… 看前面explain執行計划的截圖,嗯,沒有索引…
給t1的vc_bankacco加上索引之后


解釋執行計划:

查詢和網頁響應用時大幅縮短:

再看sql里還有三個join:

用的都是ot_dic_tdictionaryentry這張表的t4.vc_entry_value字段,那么試着給這個字段也加上索引吧,然后用時如下:

是的,時間反而變長了

explain執行計划:

所以變慢原因是:

沒加vc_entry_value的索引時,會先用vc_entry_no選出一個數量很小的表,再和t1做join,

而加了vc_entry_value的索引之后,MySQL就把這個索引用了起來,join語句被優化為先FirstMatch(ot_dic_tdictionaryentry),這產生了一個1713*1713=2934369行的中間結果(笛卡爾乘積),然后才使用vc_entry_no進行where過濾。

所以索引不能亂加啊,加錯了反而會導致性能下降!這個示例里的查詢要加索引只能在vc_entry_no上索引,而不能在vc_entry_value上!


這個示例中主要提升是IN子查詢語句的優化。在使用索引的情況下,對IN子查詢做優化前后的查詢時間分別是3.1s和0.16s

深入理解MySql子查詢IN的執行和優化
多個單列索引和聯合索引的區別詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM