這麼一條sql
SELECT tt
, nn
, cc
FROM
(
SELECT A.tt
, B.nn
, C.cc
, RANK() OVER (PARTITION BY C.ff, C.gg ORDER BY C.hh) AS R
FROM A
LEFT JOIN B ON a.mm = B.mm
LEFT JOIN C ON A.ff = C.ff AND A.gg = C.gg
) T
WHERE T.R < 2
運行10分鐘還出不來,顯然有問題。
首先想到的是加索引,給C表加了個索引,涵蓋ff, gg, 和hh字段,沒用。
問了ai後,改成
SELECT A.tt
, B.nn
, T.cc
FROM A
OUTER APPLY (
SELECT C.cc, RANK() OVER (PARTITION BY C.ff, C.gg ORDER BY C.hh) AS R
FROM C
WHERE C.ff = A.ff AND C.gg = A.gg
) T
LEFT JOIN B ON a.mm = B.mm
WHERE T.R < 2 OR T.R IS NULL
1秒就出來了。問題解決了。但原因是什麼呢?
C表很大,有將近1億條數據,A表有幾百萬條數據,表連接耗費了大量時間。而用outer apply,將相對較小的A表的每條數據在C表裏找到匹配,就快了許多。這個例子的啓示是,如果表的記錄很多,連接的開銷是很大的,rank()或類似的函數開銷也很大,可以考慮用outer apply來代替表連接。一般來説,sql鼓勵用集合運算,而outer apply有點類似遊標操作,本來直覺上好像不好。但是,在數據量很大的情況下,連接等集合操作開銷很大,反而不如將較小的一個數據集拿出來,一條條記錄去大表中找匹配,性能要好得多。
繼續優化這個sql,雖然主要問題解決了,但還有優化的餘地。首先,PARTION BY是多餘的,可以去掉,其次,這裏沒必要用RANK(),可以改成ROW_NUMBER(),性能也好一些,最後。連ROW_NUMBER()也沒必要用,直接用TOP就可以了,最後改成
SELECT A.tt
, B.nn
, T.cc
FROM A
OUTER APPLY (
SELECT TOP 1 C.cc
FROM C
WHERE C.ff = A.ff AND C.gg = A.gg
ORDER BY C.hh
) T
LEFT JOIN B ON a.mm = B.mm
OUTER APPLY/CROSS APPLY多年前用過一次,模糊記得有點類似遊標,但理解不深。這次實踐,加深了理解。