使用Listagg分析函數優化wmsys.wm_concat

2021-02-21 道森起點

   在上周末優化班的時候一個朋友拿了一個SQL出來,讓我現場優化,因為當時太忙,我安排七年老師幫忙處理。跑得慢的SQL如下:

with temp as   (select sgd.detail_id id,
wmsys.wm_concat(distinct(sg.gp_name)) groupnames,
wmsys.wm_concat(distinct(su.user_name)) usernames
                    from  sgd
                    left join  sg
                      on sg.id = sgd.gp_id
                    left join  sug
                      on sg.id = sug.gp_id
                    left join  su
                      on sug.user_id = su.id
                   group by sgd.detail_id)
                select zh.id,
                       zh.id detailid,
                       zh.name detailname,
                       zh.p_level hospitallevel,
                       zh.type hospitaltype,
                       dza.name region,
                       temp.groupnames,
                       temp.usernames,
                       (case
                         when gd.gp_id is null then
                          0
                         else
                          1
                       end) isalloted

                   from  zh
                  left join  dza
                    on zh.area_id = dza.id
                  left join temp
                    on zh.id = temp.id
                  left join (select gp_id, detail_id from sys_gp_detail where gp_Id =         :0) gd
                    on zh.id = gd.detail_id order by length(id),zh.id asc

該SQL返回20779行數據,要跑4分32秒。

該執行計劃中全是HASH JOIN,我就不貼了。
大家看我分析思路:

1. 首先這SQL最終返回20779行數據,該SQL語句最後部分沒有GROUP BY,僅僅是表關聯,並且是外連接
2. 那麼我可以判定zh也就差不多20779行數據,因為它是外連接的主表
3. 我也可以判定整個SQL裡面的表都不大,因為最終只返回20779行數據,並且沒有最終是沒有GROUP BY
4. 問題來了,既然都是小表,那為啥跑4分32秒?

遇到這種奇怪問題,我喜歡把SQL拆了。並且喜歡拆子查詢部分。所以你懂的
我們需要單獨跑with as裡面的SQL語句,跑了一下,發現居然要跑1--2分鐘
with as 的子查詢我們單獨拿出來看看

select sgd.detail_id id,
 wmsys.wm_concat(distinct(sg.gp_name)) groupnames,
wmsys.wm_concat(distinct(su.user_name)) usernames
                    from  sgd
                    left join  sg
                      on sg.id = sgd.gp_id
                    left join  sug
                      on sg.id = sug.gp_id
                    left join  su
                      on sug.user_id = su.id
                   group by sgd.detail_id

這個子查詢裡面就多了2個列轉行函數wmsys.wm_concat
把它給注釋掉單獨跑一下列,發現SQL秒殺了
現在基本上定位問題所在,就是這個wmsys.wm_concat列轉行函數引起的性能問題
於是我將上面SQL進行了部分拆分

select sgd.detail_id id, wmsys.wm_concat(distinct(sg.gp_name)) groupnames  

  from sys_gp_detail sgd  

  left join sys_gp sg on sg.id = sgd.gp_id  

 group by sgd.detail_id  

 

已用時間:  00: 00: 58.04  

  

執行計劃  

---  

Plan hash value: 3491823204  


Predicate Information (identified by operation id):  

-  

  

   2 - access("SG"."ID"(+)="SGD"."GP_ID")  

  

統計信息  

---  

          1  recursive calls  

     249348  db block gets  

      44447  consistent gets  

          0  physical reads  

          0  redo size  

    9993548  bytes sent via SQL*Net to client  

    6067828  bytes received via SQL*Net from client  

      83118  SQL*Net roundtrips to/from client  

          1  sorts (memory)  

          0  sorts (disk)  

請注意,select語句居然產生了 db block gets。db block gets一般情況下只有DML語句才會產生
select語句中除了延遲塊清除,或者有with as 產生的臨時表之外,實在想不通哪裡還能產生 db block gets
那麼這個SQL跑得慢就是慢在db block gets,相信大家對這塊沒有異議
要想成為所謂的技術大牛,必須通讀官方文檔。不管你是搞Oracle,MySQL,Hadoop,Java....等等,都必須通讀官方文檔
Oracle11g/Oracle10.2.0.5之後,wmsys.wm_concat 返回的是Clob,之前返回的是Varchar2
這就是為什麼會產生大量的db block gets,知道了這個原因,立即將這個SQL進行等價改寫,使用Listagg分析函數代替wmsys.wm_concat

wmsys.wm_concat 函數是可以支持 distinct 的,但是listagg分析函數是不支持 distinct的,所以改寫SQL的時候,需要先去重,再進行列轉行 

 select detail_id, listagg(gp_name, ',') within  

 group(  

 order by null)  

  from (select sgd.detail_id, sg.gp_name  

          from sys_gp_detail sgd  

          left join sys_gp sg on sg.id = sgd.gp_id  

         group by sgd.detail_id, sg.gp_name)  

 group by detail_id;  

   

已用時間:  00: 00: 01.12  

  

執行計劃  

---  

Plan hash value: 147456425  


Predicate Information (identified by operation id):  

-  

   4 - access("SG"."ID"(+)="SGD"."GP_ID")  

統計信息  

---  

          1  recursive calls  

          0  db block gets  

       2775  consistent gets  

          0  physical reads  

          0  redo size  

     450516  bytes sent via SQL*Net to client  

      15595  bytes received via SQL*Net from client  

       1387  SQL*Net roundtrips to/from client  

          1  sorts (memory)  

          0  sorts (disk)  

      20779  rows processed  

可以看到,SQL等價改寫之後,可以秒殺,之前需要58秒。改寫完了一個 wmsys.wm_concat ,還有另外一個 wmsys.wm_concat 需要改寫。思路一樣

select detail_id, listagg(user_name, ',') within
 group(
 order by null)
  from (select sgd.detail_id id, su.user_name
          from sgd
          left join sg on sg.id = sgd.gp_id
          left join sug on sg.id = sug.gp_id
          left join su on sug.user_id = su.id
         group by sgd.detail_id, su.user_name)
 group by detail_id;

最終將改寫好的2個結果集合併,得到等價的with as 語句,再替換原始SQL的with as語句,就可以秒殺了。

等價的with as 語句如下:

select a.detail_id id , a.groupnames, b.usernames
  from (select detail_id, listagg(gp_name, ',') within
         group(
         order by null) groupnames
          from (select sgd.detail_id, sg.gp_name
                  from sys_gp_detail sgd
                  left join sys_gp sg on sg.id = sgd.gp_id
                 group by sgd.detail_id, sg.gp_name)
         group by detail_id) a,
       (select detail_id, listagg(user_name, ',') within
         group(
         order by null) usernames
          from (select sgd.detail_id, su.user_name
                  from sgd
                  left join sg on sg.id = sgd.gp_id
                  left join sug on sg.id = sug.gp_id
                  left join su on sug.user_id = su.id
                 group by sgd.detail_id, su.user_name)
         group by detail_id) b
 where a.. detail_id = b.detail_id;

結語: 道森學院 建議各位開發人員以後在開發過程中,儘量使用listagg函數代替wmsys.wm_concat

相關焦點

  • 【函數】wm_concat包的訂製
  • Excel多區間判斷別在使用IF函數了,這個函數更簡單!
    導讀:IF函數是Excel中最基礎的邏輯函數,相信大家都非常熟悉,近期學習社群中有很多小夥伴分享了不少「喪心病狂」的IF多層嵌套函數,嵌套了七八層IF函數,那麼針對這種IF多區間嵌套函數,有什麼可以優化的地方呢?文/芒種學院@指北針(ID:lazy_info)Hi,大家早上好,這裡是芒種學院。
  • MySQL最常用分組聚合函數
    )求累加和  ①每個組函數接收一個參數  ②默認情況下,組函數忽略列值為null的行,不參與計算  ③有時,會使用關鍵字distinct剔除欄位值重複的條數注意:  1)當使用組函數的select語句中沒有group by子句時,中間結果集中的所有行自動形成一組,然後計算組函數;  2)組函數不允許嵌套
  • 由MySQL字符串函數考慮到的繞過利用
    ,通過查找以先函數來進行代替利用先對用到的函數簡單介紹ascii():返回字符串str的最左面字符的ASCII代碼值。mysql> select CONV("a",16,2); -> '1010'bin():返回數字的二進位表示形式,作為字符串值(注意:必須是數字)oct():函數返回數字N的八進位數的字符串表示,這相當於使用CONV(N,10,8)。
  • 怎樣使用Pandas批量拆分與合併Excel文件?
    pandas,python,數據分析32564機器學習常用數據集大全python,機器學習42561一個數據科學家的修煉路徑數據分析df_source.indexRangeIndex(start=0, stop=258, step=1)df_source.shape(258, 3)
  • Excel | 如何使用COUNTIFS函數
    COUNTIF函數僅能使用一個條件來進行計數。       COUNTIFS函數是多條件進行計數。
  • MYSQL 常用函數
    字符串函數函數功能
  • 推薦|Excel中Concatenate函數連接文本
    今天給大家帶來的是concatenate、concat函數連結文本。concatenate函數是把多個單元格的內容連接到一起的操作。第一步:在選定單元格內輸入=CONCATENATE()第二步:然後在括號內按順序單擊選定要連接的內容(wps要按住control選擇;mac按住command選擇)eg.
  • Oracle之CASE函數使用
    Oracle之CASE函數使用前面我們從不同維度去分析的Oracle和MySQL的區別和使用,接下來我們來分享一些Oracle的函數和特殊用法。
  • 【Excel函數教程】想在多列使用VLOOKUP函數怎麼做?
    公眾號回復2016   下載office2016使用VLOOKUP函數的確為我們帶來了極大的便利,但有時候會遇到匹配多列數據的情況,今天VLOOKUP將遇到第一個幫手如果你還不清楚VLOOKUP的基本用法,請先補課:【Excel函數教程】史上最弱的一篇vlookup教程,再學不會你就可以放棄了!使用VLOOKUP函數的確為我們帶來了極大的便利,但有時候會遇到匹配多列數據的情況,如圖所示,按照員工ID找到全部信息。
  • excel函數公式大全之 if 的使用詳解
    如果 logical_test 的計算結果為 FALSE,並且省略 value_if_false 參數的值(即,在 IF函數中, value_if_true 參數後沒有逗號),則 IF 函數返回值 0(零)。說明© 最多可以使用 64 個 IF 函數作為 value_if_true 和 value_if_false 參數進行嵌 套,以構造更詳盡的測試。
  • 技術分享 | 使用 RAND() 函數過程中發現的詭異 Bug 分析
    本文來源:原創投稿*愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。背景MySQL 中的 RAND() 函數是一個隨機數發生器,可以返回一個 >=0 並 <1.0 的隨機浮點數。最近在實際使用過程裡遇見了一個主流版本中非常詭異的 Bug,故整理出來,以免大家踩坑。
  • FIND函數使用方法
    FIND函數使用方法導讀EXCEL函數是重點部分,也是比較難學的部分,很多同學因為它的難度選擇了放棄,其實函數也沒有想像的那麼難
  • IF函數與COUNTA函數嵌套使用方法總結
    晴哥錄製的用VBA編制現金流量表的課件中,在透視匯總表裡給科目名稱標序號時使用了IF函數和COUNTA函數,當然,這兩個函數的使用是在VBA裡完成的
  • 如何使用EXCEL函數(公式)
    下圖使用SUM函數計算銷售部人員的薪資之和。嵌套函數嵌套函數是指一個函數的輸出值作為另一個函數的參數出現。如下圖,函數SUM作為函數IF的參數。使用函數第一步:選擇輸出值存放的單元格,如G2第二步:在 「公式」選項卡中選擇要使用的函數,如「
  • 定量分析方法第09講:矩陣分析基礎一
    定量分析方法第09講:矩陣分析基礎一矩陣基本運算、特殊矩陣、行列式、秩和範數。
  • IF函數的基本使用方法
    當表格中的單元格需要根據不同條件自動填充結果時,常常會使用到IF函數,IF函數怎麼寫多個條件呢?