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

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

關於regexp_substr函數的使用可查看[Oracle通過一個字段的值將一條記錄拆分爲多條記錄](http://fanjiajia.cn/2019/08/16/SQL/flx1/)

需求和表結構

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

  • 船舶動態表CBDT(肯定是省略的啦,哪有這麼簡單的表)

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本身的問題,去掉regexpsubstr後,果然,只需要0.0xx秒的時間,所以基本確定了是這個函數的問題。開始度娘和谷歌。然而只找到了一個百度經驗說性能問題,也沒有說怎麼解決。直到在谷歌上有人說,regexpsubstr是正則,其本身效率就不高,不推薦。但是不推薦如前我的需求是必須要用啊(不知道有沒有其他方案),找了許久依舊沒有解決方案,回頭再觀察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函數的一次調優

生命不息,使勁造。

本文內容個人拙見,若有出入,歡迎指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章