SQL效率之where子句中的子查詢和函數

MorrisPresl 8年前發布 | 8K 次閱讀 SQL Oracle數據庫

工作中,曾有同事問我以下 sql 的效率如何,這里擴展一下這個問題并進行分析,主要說明 where 子句中的子查詢和函數執行次數及索引使用情況 。

select * from trd_fundjour a  

where oc_date = (select collect_date from hscon.sys_arg);

首先來看一下表的數據分布情況:

表 trd_fundjour 是分區表, 里面是按月分區的,以oc_date為索引。

先看一下 201605 分區全掃描產生多少邏輯讀。

這里產生了 88 個一致讀, 接下來執行前面的 sql 。

這里比前一次多出了 7 個一致讀,也多出了對 sys_arg 表的掃描,單獨查詢 sys_arg 表看看。

一致讀剛好是 7 個,從這里可以看出,子查詢只執行了一次,而不是針對分區中的每條記錄比較一次,因為這個分區中有 3279 條記錄。

下面來看能否用上索引,這里強制走索引看看。

顯然,在用 hint 強制的情況下索引是可以走的。就本例而言,走索引比全表掃描效率會更高,但在不用 hint 的情況下 Oracle 選的卻是全表掃描。雖然從表面上看是選擇了較差的執行計劃,其實是正常的,因為在生成執行計劃的時候 Oracle 得不到子查詢的值,無法判斷全表掃描和索引掃描哪個更優,因此選擇全表掃描就無可厚非了。所以,在可能的情況下應該將這些非相關子查詢執行結果賦給變量,把上面的 sql 拆成兩個語句來執行。

下面來看一下函數的表現, hscon.f_get_collectdate 的實現邏輯就是封裝前面 sql 中的子查詢,返回查詢結果。

將子查詢改成函數,可以看到遞歸調用和一致讀都大了很多,是哪里產生的呢?我們來單獨查一下函數看看。

從這里可以看出,函數調用一次就產生 1 個遞歸調用和 7 個一致讀。 3286*7 (函數執行一次 7 個一致讀)加上 88 (分區全掃描產生的一致讀)剛好是 23090 ,也就是說在前面的語句中函數執行了 3286 次!上文這一數字其實可以被拆分成 3279+7 兩部分來看, 3279 是分區的記錄數, 7 應該是和分區消除相關。我們可以得出這樣的結論:針對分區中的每一條記錄,函數都會調用一次進行比較。

既然前面的寫法會導致函數頻繁調用,我們修改一下 sql 寫法,先把函數生成一個結果集 。

從上圖看,情況更加糟糕,但其實并不是我們的想法錯了,而是 Oracle 太“聰明”了。從執行計劃的 filter 中可以看出, Oracle 重寫了我們的 sql ,合并了子查詢,基本上相當于又給改回了原樣。下面加個 hint 來防止 Oracle 的這種自作聰明。

情況有了很大的改善。雖說與直接使用子查詢方式相比,使用函數在一致讀上還是大了一些,這個差別懷疑是和分區表有關(非分區表應該沒有差別,未做驗證),并且實際上可以通過調整執行計劃來達到無差別,這里不做詳細說明。

下面看一下索引的使用情況。

同樣地,也是可以走索引的,但 Oracle 不會主動使用,需要用 hint 強制,理由前面已經提過了。需要注意的是,這里遞歸調用只有 8 次,和分區全掃描時的表現完全不同。

再看下面的語句。

這應該是比較合理的執行計劃了。

綜上所述,通常使用子查詢的效率比用函數都要來得好一些,個別情況下用函數的效率很糟糕。

最高效的方法就是盡可能拆分成兩個語句,用變量來代替子查詢和函數。

 

來自: http://rdcqii.hundsun.com/portal/article/473.html

 

 本文由用戶 MorrisPresl 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!