遇到性能問題的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改寫又需要添加有效的索引。