一條update語句優化小記


遇到性能問題的sql如下:
sql1:
UPDATE amlclientlevel a
SET    a.client_value = (SELECT l.client_value
                         FROM   amlclientdynamiclevel l
                         WHERE  l.inner_client_id = a.inner_client_id)
WHERE  a.client_value = 0
       AND a.need_value = '0'
       AND a.client_status = '0'
       AND EXISTS (SELECT 1
                   FROM   amlclientdynamiclevel l
                   WHERE  l.inner_client_id = a.inner_client_id);
此sql執行超過兩個小時跑不出結果。兩張表都是幾百兆大小的小表,按照常理小表不應該產生性能問題。
 
執行計划1:
 

 

amlclientlevel 表上的索引

 

amlclientdynamiclevel 表上的索引
 

 

問題發生的原因有兩點
 
第一點:上訴update的寫法會導致amlclientlevel和amlclientdynamiclevel在兩個地方產生關聯,一處是EXISTS一處是set。
SET  a.client_value = (SELECT l.client_value
                         FROM   amlclientdynamiclevel l
                         WHERE  l.inner_client_id = a.inner_client_id)
這樣的set寫法執行的過程其實是類比標量子查詢的執行過程。對應update的每一個返回行都需要。
 
小知識點:
8 - filter(NVL("L"."INNER_CLIENT_ID",' ')=:B1)
sql本身沒有綁定變量,在執行計划中 出現綁定變量值,應該是執行計划中出現了傳值。應該留意這樣的情況,傳值可能導致嚴重的性能問題。
 
 
第二點:生成index hash join VIEW 。本身這樣的執行步驟效率不高,結合set 引用的類比標量子查詢的更新方式,導致數萬次的index hash join VIEW生成及過濾數據成了整個sql的瓶頸。
 
執行計划2:

 

 
解決思路其實也是兩點
第一點:改善set寫法讓其不再發生類比標量子查詢的執行過程,減少一個關聯。改寫update為merge into的寫法。
sql2:
MERGE /*+  GATHER_PLAN_STATISTICS J1 */ INTO AMLCLIENTLEVEL_BAK A
   USING AMLCLIENTDYNAMICLEVEL_BAK L
                         ON (L.INNER_CLIENT_ID = A.INNER_CLIENT_ID)
WHEN MATCHED THEN
UPDATE  SET A.CLIENT_VALUE = L.CLIENT_VALUE
WHERE A.CLIENT_VALUE = 0
   AND A.NEED_VALUE = '0'
   AND A.CLIENT_STATUS = '0';
 
執行計划3:
 
優化后的執行效率為13s。上面的執行計划缺少有效的數據過濾后再進行兩表關聯。
 
嘗試改寫merge讓其先進行數據過濾
sql3:
MERGE /*+ GATHER_PLAN_STATISTICS J1 */ INTO (select * from AMLCLIENTLEVEL_BAK A WHERE A.CLIENT_VALUE = 0
   AND A.NEED_VALUE = '0'
   AND A.CLIENT_STATUS = '0') A
   USING AMLCLIENTDYNAMICLEVEL_BAK L
                         ON (L.INNER_CLIENT_ID = A.INNER_CLIENT_ID)
WHEN MATCHED THEN
UPDATE  SET A.CLIENT_VALUE = L.CLIENT_VALUE;
本次改寫未起到先進行數據過濾作用,oracle內部選取的執行計划依舊是執行計划3。
 
實際上在表上添加了兩個有效的索引后,sql3的執行計划(執行計划4)才是先進行數據過濾,效率已經提高。
create index AMLCLIENTLEVEL_BAK_IDX01 on AMLCLIENTLEVEL_BAK (CLIENT_VALUE, NEED_VALUE);
create index AMLCLIENTDYNAMICLEVEL_B_IDX1 on AMLCLIENTDYNAMICLEVEL_BAK (INNER_CLIENT_ID, CLIENT_VALUE);
 
執行計划4:
 

 

小知識點:
ON (L.INNER_CLIENT_ID = A.INNER_CLIENT_ID and  A.CLIENT_VALUE = 0
   AND A.NEED_VALUE = '0'
   AND A.CLIENT_STATUS = '0')
SET中的字段不能出現在ON的關聯條件中
 
第二點:優化index hash join VIEW,建立有效的索引,引導正確的sql的執行計划。
 
create index AMLCLIENTLEVEL_BAK_IDX01 on AMLCLIENTLEVEL_BAK (CLIENT_VALUE, NEED_VALUE);
create index AMLCLIENTDYNAMICLEVEL_B_IDX1 on AMLCLIENTDYNAMICLEVEL_BAK (INNER_CLIENT_ID, CLIENT_VALUE);
 
嘗試建議索引不進行sql改寫后的執行效率也是很高的。
執行計划5:

 

 
 
既然這樣還有必要改寫sql嗎?
1、首先建立索引是比較危險的,可能導致其他sql出現性能問題,正確的方式是需要測試驗證后決定。
2、針對不同業務系統數據處理情況決定,本次案例中數據量小,展示不出update和merge性能上的巨大差距。
3、如果數據量非常大,可能既需要sql改寫又需要添加有效的索引。
 
 


免責聲明!

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



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