記Oracle中regexp_substr的一次調優(速度提高95.5%)


項目中需要做一個船舶代理費的功能,針對代理的船進行收費,那么該功能的第一步便是選擇進行代理費用信息的錄入,在進行船舶選擇的時候,發現加載相關船舶信息十分的慢,其主要在sql語句的執行,因為測試的時候數據較少,實際使用中,數據量較大。

關於regexp_substr函數的使用可查看Oracle通過一個字段的值將一條記錄拆分為多條記錄

需求和表結構

船舶相關的信息在系統中有船舶動態表(CBDT),另外有一張船舶代理費表(CBDLF),要求對於已經錄入代理費的船舶不再出現在列表中(CBDLF表中有記錄的需要過濾掉),CBDT中有一個合同清單字段,HTQD,該字段由分號";"拼接多個合同,由於選了船舶,需要計算這個船上所有合同的作業量(拿合同字段和其他表做連接),因此需要切割,方便后繼的作業量計算,需求引入就是這里——需要切割合同清單字段(HTQD),存在幾個合同,就要將該行變成幾條記錄。

  • 船舶動態表CBDT(肯定是省略的啦,哪有這么簡單的表)
CBBH HC HTQD
0001 191210 N20191202-xx;N20191203-xx
  • 船舶動態表CBDLF
CBBH HC Free
0001 191210 12534.23

原來的方案

對於之前的sql,執行時間長達5秒多,最快也是4秒多,而且是只有一個月的數據。
原本方案的執行時間

看看原來的sql語句

select CBDT.CBBH, CBDT.HC,
  regexp_substr(CBDT.HTBHQD,'[^;]+', 1,LEVEL,'i') HTTDBH
FROM CBDT 
	WHERE (CBDT.CBBH, CBDT.HC) not IN (SELECT CBBH, HC from CBDLFB)		
    AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
connect by LEVEL <=regexp_count(CBDT.HTBHQD, ';') + 1

第一次嘗試

使用了not in,顯然這滿足要求,但事實是not in的效率是十分低下的,(當初在用的時候,我也不知道啊,手動捂臉),所以應該改成join,有了下面的sql

select CBDT.CBBH, CBDT.HC,
  regexp_substr(CBDT.HTBHQD,'[^;]+', 1,LEVEL,'i') HTTDBH
FROM CBDT 
LEFT JOIN CBDLFB ON CBDT.CBBH = CBDLFB.CBBH and CBDT.HC = CBDLFB.HC
	WHERE CBDLFB.CBBH is NULL	
    AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
connect by LEVEL <=regexp_count(CBDT.HTBHQD, ';') + 1

這樣改了之后,基本維持在4秒左右,當然,這還是不能忍的啊。

第二次嘗試

通過改變時間,無論是延長還是縮短,sql執行的時間基本都在4秒左右,所以,目前的數據量對sql的影響不是很大了,那么肯定是sql本身的問題,去掉regexp_substr后,果然,只需要0.0xx秒的時間,所以基本確定了是這個函數的問題。開始度娘和谷歌。然而只找到了一個百度經驗說性能問題,也沒有說怎么解決。直到在谷歌上有人說,regexp_substr是正則,其本身效率就不高,不推薦。但是不推薦如前我的需求是必須要用啊(不知道有沒有其他方案),找了許久依舊沒有解決方案,回頭再觀察sql,regexp_substr是正則表達式毫無疑問,然后發現最后的regexp_count,這個那應該也是正則,但是regexp_count(CBDT.HTBHQD, ';')的意思是計算有幾個分號,這個函數可以換掉啊。所以改用了LENGTH(CBDT.HTBHQD) -LENGTH(REPLACE(CBDT.HTBHQD,';','')) + 1 ,運行,奇跡發生了。
新的sql

select CBDT.CBBH, CBDT.HC,
  regexp_substr(CBDT.HTBHQD,'[^;]+', 1,LEVEL,'i') HTTDBH
FROM CBDT 
LEFT JOIN CBDLFB ON CBDT.CBBH = CBDLFB.CBBH and CBDT.HC = CBDLFB.HC
	WHERE CBDLFB.CBBH is NULL	
    AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
connect by LEVEL <= LENGTH(CBDT.HTBHQD) -LENGTH(REPLACE(CBDT.HTBHQD,';','')) + 1

新的sql執行時間
速度提到了約:67%。

1秒多的時間,雖然較原來的5秒要好太多,但是1秒多的卡頓,始終還是不好,那么繼續嘗試吧。

找新的方案去了,待更新........(不到1秒內,誓不回);

——————————————我是分割線——————————————

我回來了,因為找到了終極優化,從5秒到0.025s,還是谷歌啊。
最后執行時間
最后執行時間
話不多說,直接看改后的sql

select CBDT.CBBH, CBDT.HC,
  regexp_substr(CBDT.HTBHQD,'[^;]+', 1,l) HTTDBH -- 原來的LEVEL換成了l,注意
FROM CBDT 
LEFT JOIN CBDLFB ON CBDT.CBBH = CBDLFB.CBBH and CBDT.HC = CBDLFB.HC,
	(SELECT LEVEL l FROM DUAL CONNECT BY LEVEL<=100) b -- 關鍵
	WHERE CBDLFB.CBBH is NULL	
    AND KBRQ >= TO_DATE('2019-11-18', 'yyyy-mm-dd') and KBRQ <= TO_DATE('2019-12-18', 'yyyy-mm-dd')
   AND l <= LENGTH(CBDT.HTBHQD) -LENGTH(REPLACE(CBDT.HTBHQD,';','')) + 1  

之前的connect 是使用到sql最后,這樣的方式會導致數據出現很多冗余,而且冗余特別嚴重,需要使用distinct,至於原因,還在找。使用regexp_substr函數必須配對使用connect,但是沒想到居然可以這樣使用。

5—>0.023 這速度提高99.5%;頁面秒開,爽。

最后

本文可在我的小站中查看記Oracle中regexp_substr函數的一次調優

生命不息,使勁造。


免責聲明!

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



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