1. 程式人生 > 其它 >merge語句導致的CPU使用率過高的優化(二) (r7筆記第9天)

merge語句導致的CPU使用率過高的優化(二) (r7筆記第9天)

之前分享過一篇關於merge語句導致的CPU使用率過高優化的案例。http://blog.itpub.net/23718752/viewspace-1819471/ 後續的跟進沒有補充,也“秀”一張圖,紅色的火焰是原來的系統負載,右邊的部分是最近的邏輯讀情況,不過慚愧的是,這個不是優化的效果,因為應用的高峰期 已經處理完了,後面的sql呼叫頻率極低,所以感覺不到任何的壓力。所以通過這個圖也可以看出,給一張差別巨大的圖也不一定是系統優化的效果,也可能是其 它外在因素。

那麼既然要說跟進,後面的情節才夠真實和現實,開發同學找到語句,修改花了些時間,今天突然聯絡到我,說已經修改完成了。我也從v$sql中抓取了幾條語句,發現執行計劃已經改變。 感覺這件事情就要告一段落,但是開發的同事過了一會找到我說,他們在應用端發現日誌中出現了ORA-00001的錯誤。 ### Cause: java.sql.SQLException: ORA-00001: unique constraint (AXXXX.OPENPLATFORM_USER) violated 這個問題著實在意料之外,他們反饋出現問題後,立即回退了程式碼,但是日誌保留了下來,讓我看看是什麼問題。找開發要繫結變數的值,貌似還比較困難,那就算了,自己分析吧。 比如還是簡單模擬這個錯誤。 CREATE TABLE TEST(ID NUMBER,NAME VARCHAR(100)); ALTER TABLE TEST MODIFY(ID UNIQUE); INSERT INTO TEST VALUES(100,'BB_NOT_MATCHED'); INSERT INTO TEST VALUES(1000,'BBB_NOT_MATCHED'); SQL> select*from test; ID NAME ---------- -------------------- 1000 BB_NOT_MATCHED 100 BBB_NOT_MATCHED 我們已經插入了兩條值,這個時候來嘗試一下,update是否生效 SQL> MERGE INTO TEST t USING (SELECT ID from TEST where ID=1000 union select -1 id from dual ) tw ON (tw.ID=T.ID) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 1000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(1000,'BBB_NOT_MATCHED') ; MERGE INTO TEST t * ERROR at line 1: ORA-00001: unique constraint (TEST.SYS_C0011234) violated 這個錯誤還是有些奇怪,本來預計的update變成了insert,結果還違反了唯一性約束。 來看看最初始版本的執行情況。 SQL> MERGE INTO TEST t USING (SELECT count(*) CNT from TEST where ID=1000 ) tw ON (tw.CNT>0) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID=1000 WHEN NOT MATCHED THEN INSERT(ID,NAME) VALUES(1000,'BBB_NOT_MATCHED'); 1 row merged. 發現確實是做了update,把id=1000的行,name列修改成了AAA_MATCHED SQL> select *from test; ID NAME ---------- -------------------- 1000 AAA_MATCHED 100 BBB_NOT_MATCHED 手工把資料改回來,繼續測試。 SQL> update test set name='BBB_NOT_MATCHED' where id=1000; 1 row updated. SQL> select *from test; ID NAME ---------- -------------------- 1000 BBB_NOT_MATCHED 100 BBB_NOT_MATCHED 這個時候開發的同學突然給我反饋說,他們看如果把union all的字句取消就不報錯了。原來他們也在debug,我告訴他們,不報錯不代表沒錯,還是需要搞明白最根本的原因。 MERGE INTO TEST t USING (SELECT ID from TEST where ID=1000 ) tw ON (tw.ID=T.ID) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 1000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(1000,'BBB_NOT_MATCHED') ; SQL> select *from test; ID NAME ---------- -------------------- 1000 AAA_MATCHED 100 BBB_NOT_MATCHED 這個時候update確實能夠正常執行,似乎也是預期的結果,那麼做一條insert,看看效果。 MERGE INTO TEST t USING (SELECT ID from TEST where ID=2000 ) tw ON (tw.ID=T.ID) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 2000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ; 0 rows merged. 可以看到,id=2000的行沒有插入資料。這個我覺得也就是為什麼開發的同學沒有選用這個方法的根本原因。但是似乎他們沒有找到更好的方法, 那麼繼續改進,就是我上次分享的,加入union all的部分。 MERGE INTO TEST t USING (SELECT ID from TEST where ID=2000 union all select -999 from dual ) tw ON (tw.ID=T.ID) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 2000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ; 1 row merged. SQL> select *from test; ID NAME ---------- -------------------- 1000 AAA_MATCHED 100 BBB_NOT_MATCHED 2000 BBB_NOT_MATCHED 這個時候問題來了,insert可以了,但是update有問題了。 SQL> MERGE INTO TEST t 2 USING (SELECT ID from TEST where ID=2000 union all select -999 from dual ) tw 3 ON (tw.ID=T.ID ) 4 WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' 5 where ID= 2000 6 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ; MERGE INTO TEST t * ERROR at line 1: ORA-00001: unique constraint (TEST.SYS_C0011234) violated 如果你看暈了,我來整理一下思路。 ###加入union all的方案 不存在id=2000 可以insert 已存在id=2000 update 報ora-00001 ### 去掉union all子句 不存在id=2000 可以插入 已存在id=2000 update無法執行 那麼來一個動態的條件,可以不? SQL> MERGE INTO TEST t USING (SELECT ID from TEST where ID=2000 union all select -999 from dual) tw ON (tw.ID=T.ID or tw.id=-999) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 2000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ; MERGE INTO TEST t * ERROR at line 1: ORA-30926: unable to get a stable set of rows in the source tables 所以這些思路都不同,但是根據id來決定Inert,update也算一個常規問題,吃完晚飯繼續琢磨,總算找到了一個合適的方法。 SQL> MERGE INTO TEST t USING (SELECT 2000 id FROM dual

) tw ON (tw.ID=T.ID ) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 2000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ; 1 row merged. 這種情況下條件是唯一性匹配的,匹配與否就很清晰了。 當前資料情況如下: SQL> select *from test; ID NAME ---------- -------------------- 1000 AAA_MATCHED 100 BBB_NOT_MATCHED 2000 AAA_MATCHED
插入一條新資料 SQL> MERGE INTO TEST t USING (SELECT 3000 id FROM dual ) tw ON (tw.ID=T.ID ) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 3000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(3000,'BBB_NOT_MATCHED') ; 1 row merged. SQL> select *from test where id=3000; ID NAME ---------- -------------------- 3000 BBB_NOT_MATCHED
更新一條記錄 SQL> MERGE INTO TEST t USING (SELECT 3000 id FROM dual ) tw ON (tw.ID=T.ID ) WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID= 3000 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(3000,'BBB_NOT_MATCHED') ; SQL> select *from test where id=3000; ID NAME ---------- -------------------- 3000 AAA_MATCHED 改進後的執行計劃如下: Execution Plan ---------------------------------------------------------- Plan hash value: 3869333021 ----------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------------------- | 0 | MERGE STATEMENT | | 1 | 78 | 3 (0)| 00:00:01 | | 1 | MERGE | TEST | | | | | | 2 | VIEW | | | | | | | 3 | NESTED LOOPS OUTER | | 1 | 79 | 3 (0)| 00:00:01 | | 4 | TABLE ACCESS FULL | DUAL | 1 | 2 | 2 (0)| 00:00:01 | | 5 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 77 | 1 (0)| 00:00:01 | |* 6 | INDEX UNIQUE SCAN | SYS_C0011234 | 1 | | 0 (0)| 00:00:01 | ----------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 6 - access("T"."ID"(+)=CASE WHEN (ROWID IS NOT NULL) THEN 3000 ELSE 3000 END ) 1 row merged. 所以正式環境的語句也是類似的思路。 MERGE INTO UC_OPENPLATFORM_USER t USING (SELECT :1 USER_ID,:2 PLATFORM from DUAL ) tw ON (tw.USER_ID=T.USER_ID and tw.PLATFORM=t.PLATFORM) WHEN MATCHED THEN UPDATE SET t.NAME=:3, t.UPDATE_DATE=SYSDATE where USER_ID=:4 and PLATFORM=:5 WHEN NOT MATCHED THEN INSERT(USER_ID, PLATFORM, NAME, CREATE_DATE, UPDATE_DATE) VALUES(:6, :7, :8, SYSDATE, SYSDATE) 改動之後還是需要再部署測試,相信大體就沒有問題了。 通過這個案例可以發現,很多優化的時候從執行計劃等情況確實有了很大的提升,一些瓶頸也得到了解決,但是還是要更周密的測試,別修復了一個錯,引來更多的問題。而且sql上線也要評估,進行驗收測試。儘可能把問題都解決在溝通層面,不用那麼多的郵件來標註,說明。