Oracle Update語句中多表關聯中被關聯表多次全表掃描問題
Oracle Update語句中多表關聯中被關聯表多次全表掃描問題
前言
最近優化了一個update語句中,多表關聯導致表多次全表掃描的效能問題。
嘗試用merge into改寫後發現原來不知道多久能執行完的語句達到秒級別執行完,因為merge into可以避免多次的全表掃描。
比較好模擬,接下來模擬一下,也好記錄一下有些遇到的小問題。
事故模擬
這個是我實際生產優化的資料和語句的模擬。
drop table t1 purge; drop table t2 purge; create table t1 as select * from dba_objects where rownum<=View Code100; create table t2 as select * from dba_objects where rownum<=50;
10:34:30 SYS@zkm(1)> drop table t1 purge; Table dropped. Elapsed: 00:00:00.01 10:35:27 SYS@zkm(1)> drop table t2 purge; Table dropped. Elapsed: 00:00:00.01 10:35:27 SYS@zkm(1)> create table t1 as select * from dba_objects whererownum<=100; Table created. Elapsed: 00:00:00.01 10:35:27 SYS@zkm(1)> create table t2 as select * from dba_objects where rownum<=50; Table created. Elapsed: 00:00:00.01
update的語句如下:
update t1 set (t1.owner, t1.object_name) = (select t2.owner, t2.object_name from t2 where t1.object_id= t2.object_id) where exists (select 1 from t2 where t1.object_id = t2.object_id);
開啟statistics_level為all,看看執行後對被關聯表t2的執行次數是多少次。
10:43:41 SYS@zkm(1)> set pagesize 9999 long 9999 line 500 timing on 10:45:14 SYS@zkm(1)> alter session set statistics_level=all; Session altered. Elapsed: 00:00:00.00 10:45:14 SYS@zkm(1)> update t1 10:45:19 2 set (t1.owner, t1.object_name) = 10:45:19 3 (select t2.owner, t2.object_name 10:45:19 4 from t2 10:45:19 5 where t1.object_id = t2.object_id) 10:45:19 6 where exists (select 1 from t2 where t1.object_id = t2.object_id); 50 rows updated. Elapsed: 00:00:00.00 10:45:20 SYS@zkm(1)> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- SQL_ID aq3ptnvdvmnpq, child number 0 ------------------------------------- update t1 set (t1.owner, t1.object_name) = (select t2.owner, t2.object_name from t2 where t1.object_id = t2.object_id) where exists (select 1 from t2 where t1.object_id = t2.object_id) Plan hash value: 1571583455 ---------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | ---------------------------------------------------------------------------------------------------------------------- | 0 | UPDATE STATEMENT | | 1 | | 0 |00:00:00.01 | 209 | | | | | 1 | UPDATE | T1 | 1 | | 0 |00:00:00.01 | 209 | | | | |* 2 | HASH JOIN RIGHT SEMI| | 1 | 100 | 50 |00:00:00.01 | 7 | 2440K| 2440K| 1515K (0)| | 3 | VIEW | VW_SQ_1 | 1 | 50 | 50 |00:00:00.01 | 3 | | | | | 4 | TABLE ACCESS FULL | T2 | 1 | 50 | 50 |00:00:00.01 | 3 | | | | | 5 | TABLE ACCESS FULL | T1 | 1 | 100 | 100 |00:00:00.01 | 4 | | | | |* 6 | TABLE ACCESS FULL | T2 | 50 | 1 | 50 |00:00:00.01 | 150 | | | | ---------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("T1"."OBJECT_ID"="ITEM_1") 6 - filter("T2"."OBJECT_ID"=:B1) Note ----- - dynamic sampling used for this statement (level=2) 31 rows selected. Elapsed: 00:00:00.01 10:45:22 SYS@zkm(1)> rollback; Rollback complete. Elapsed: 00:00:00.01 10:45:24 SYS@zkm(1)>
可以看到id=6的starts的值為50,表示id為6的t2全表掃描被執行了50次,在生產事故中,t2表是比較大的,並且掃描次數相對較多,因此update執行時間是非常久的。
這裡id=1的子步驟為id=2和id=6,此處的id=1的UPDATE操作類似和“NESTED LOOPS”一樣,id=2出有多少條符合條件的記錄,則id=6就執行多少次。
比如這裡的,t1被update的記錄通過“where exists (select 1 from t2 where t1.object_id = t2.object_id)”後,仍有50行符合可以被update,因此這50行中每行都會去全表掃t2一次。
10:56:00 SYS@zkm(1)> select count(*) from t1 where exists (select 1 from t2 where t1.object_id = t2.object_id); COUNT(*) ---------- 50 Elapsed: 00:00:00.00
使用merge into改造後,效率變高(資料量小看不出執行時間的差別,你可以自己構造巨量資料去比較,這裡主要看t2全表掃描的次數),
merge into t1 using t2 on (t1.object_id = t2.object_id) when matched then update set t1.owner = t2.owner, t1.object_name = t2.object_name;View Code
11:00:23 SYS@zkm(1)> merge into t1 11:00:23 2 using t2 11:00:23 3 on (t1.object_id = t2.object_id) 11:00:23 4 when matched then 11:00:23 5 update set t1.owner = t2.owner, t1.object_name = t2.object_name; 50 rows merged. Elapsed: 00:00:00.00 11:00:23 SYS@zkm(1)> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- SQL_ID agt8fym6y8hug, child number 0 ------------------------------------- merge into t1 using t2 on (t1.object_id = t2.object_id) when matched then update set t1.owner = t2.owner, t1.object_name = t2.object_name Plan hash value: 2683531971 ------------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | ------------------------------------------------------------------------------------------------------------------ | 0 | MERGE STATEMENT | | 1 | | 0 |00:00:00.01 | 59 | | | | | 1 | MERGE | T1 | 1 | | 0 |00:00:00.01 | 59 | | | | | 2 | VIEW | | 1 | | 50 |00:00:00.01 | 7 | | | | |* 3 | HASH JOIN | | 1 | 50 | 50 |00:00:00.01 | 7 | 870K| 870K| 1289K (0)| | 4 | TABLE ACCESS FULL| T2 | 1 | 50 | 50 |00:00:00.01 | 3 | | | | | 5 | TABLE ACCESS FULL| T1 | 1 | 100 | 100 |00:00:00.01 | 4 | | | | ------------------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID") Note ----- - dynamic sampling used for this statement (level=2) 27 rows selected. Elapsed: 00:00:00.01 11:00:26 SYS@zkm(1)> rollback; Rollback complete. Elapsed: 00:00:00.00
對比可以知道,merge into的t2只掃描了一次,並且其他維度比如Buffers,OMem,1Mem,Used-Mem都減少了。
merge into的限制
重新構造另外的資料環境。
drop table t1 purge; drop table t2 purge; create table t1 ( id number,name varchar2(20)); create table t2 ( id number,name varchar2(20)); insert into t1 values (1,'t1a'); insert into t1 values (2,'t1b'); insert into t1 values (3,'t1c'); insert into t1 values (4,'t1d'); insert into t1 values (5,'t1e'); insert into t1 values (6,'t1f'); insert into t2 values (2,'t2a'); insert into t2 values (3,'t2b'); insert into t2 values (4,'t2c'); insert into t2 values (5,'t2d'); insert into t2 values (6,'t2e'); insert into t2 values (7,'t2f'); commit;View Code
11:04:36 SYS@zkm(1)> drop table t1 purge; Table dropped. Elapsed: 00:00:00.01 11:04:42 SYS@zkm(1)> drop table t2 purge; Table dropped. Elapsed: 00:00:00.01 11:04:43 SYS@zkm(1)> create table t1 ( id number,name varchar2(20)); Table created. Elapsed: 00:00:00.01 11:04:49 SYS@zkm(1)> create table t2 ( id number,name varchar2(20)); Table created. Elapsed: 00:00:00.00 11:04:49 SYS@zkm(1)> insert into t1 values (1,'t1a'); 1 row created. Elapsed: 00:00:00.00 11:04:52 SYS@zkm(1)> insert into t1 values (2,'t1b'); 1 row created. Elapsed: 00:00:00.00 11:04:52 SYS@zkm(1)> insert into t1 values (3,'t1c'); 1 row created. Elapsed: 00:00:00.01 11:04:52 SYS@zkm(1)> insert into t1 values (4,'t1d'); 1 row created. Elapsed: 00:00:00.00 11:04:52 SYS@zkm(1)> insert into t1 values (5,'t1e'); 1 row created. Elapsed: 00:00:00.00 11:04:52 SYS@zkm(1)> insert into t1 values (6,'t1f'); 1 row created. Elapsed: 00:00:00.00 11:04:53 SYS@zkm(1)> insert into t2 values (2,'t2a'); 1 row created. Elapsed: 00:00:00.00 11:04:56 SYS@zkm(1)> insert into t2 values (3,'t2b'); 1 row created. Elapsed: 00:00:00.00 11:04:56 SYS@zkm(1)> insert into t2 values (4,'t2c'); 1 row created. Elapsed: 00:00:00.00 11:04:56 SYS@zkm(1)> insert into t2 values (5,'t2d'); 1 row created. Elapsed: 00:00:00.00 11:04:56 SYS@zkm(1)> insert into t2 values (6,'t2e'); 1 row created. Elapsed: 00:00:00.00 11:04:56 SYS@zkm(1)> insert into t2 values (7,'t2f'); 1 row created. Elapsed: 00:00:00.00 11:04:56 SYS@zkm(1)> commit; Commit complete. Elapsed: 00:00:00.00 11:04:57 SYS@zkm(1)> select * from t1; ID NAME ---------- ------------------------------------------------------------ 1 t1a 2 t1b 3 t1c 4 t1d 5 t1e 6 t1f 6 rows selected. Elapsed: 00:00:00.00 11:05:15 SYS@zkm(1)> select * from t2; ID NAME ---------- ------------------------------------------------------------ 2 t2a 3 t2b 4 t2c 5 t2d 6 t2e 7 t2f 6 rows selected. Elapsed: 00:00:00.00
對於update語句,
update t1 set (t1.id,t1.name)=(select t2.id,t2.name from t2 where t1.id=t2.id) where exists (select t2.id,t2.name from t2 where t1.id=t2.id);
無法改造成merge into,如下:
merge into t1 using t2 on (t1.id = t2.id) when matched then update set t1.id = t2.id, t1.name = t2.name;
因為merge into中不能對匹配欄位id進行update,會報ORA-38104,
11:12:44 SYS@zkm(1)> merge into t1 11:13:18 2 using t2 11:13:18 3 on (t1.id = t2.id) 11:13:18 4 when matched then 11:13:18 5 update set t1.id = t2.id, t1.name = t2.name; on (t1.id = t2.id) * ERROR at line 3: ORA-38104: Columns referenced in the ON Clause cannot be updated: "T1"."ID" Elapsed: 00:00:00.00
這種情況下,我只能想到在t2的id欄位建立索引,避免多次的全表掃描。
11:15:33 SYS@zkm(1)> set pagesize 9999 long 9999 line 500 timing on 11:15:34 SYS@zkm(1)> alter session set statistics_level=all; Session altered. Elapsed: 00:00:00.00 11:15:34 SYS@zkm(1)> update t1 set (t1.id,t1.name)=(select t2.id,t2.name from t2 where t1.id=t2.id) where exists (select t2.id,t2.name from t2 where t1.id=t2.id); 5 rows updated. Elapsed: 00:00:00.00 11:15:35 SYS@zkm(1)> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- SQL_ID c124snsjck4d6, child number 1 ------------------------------------- update t1 set (t1.id,t1.name)=(select t2.id,t2.name from t2 where t1.id=t2.id) where exists (select t2.id,t2.name from t2 where t1.id=t2.id) Plan hash value: 2611650519 ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ------------------------------------------------------------------------------------------------- | 0 | UPDATE STATEMENT | | 1 | | 0 |00:00:00.01 | 22 | | 1 | UPDATE | T1 | 1 | | 0 |00:00:00.01 | 22 | |* 2 | FILTER | | 1 | | 5 |00:00:00.01 | 9 | | 3 | TABLE ACCESS FULL | T1 | 1 | 6 | 6 |00:00:00.01 | 3 | |* 4 | INDEX RANGE SCAN | IDX_ID | 6 | 1 | 5 |00:00:00.01 | 6 | | 5 | TABLE ACCESS BY INDEX ROWID| T2 | 5 | 1 | 5 |00:00:00.01 | 10 | |* 6 | INDEX RANGE SCAN | IDX_ID | 5 | 1 | 5 |00:00:00.01 | 5 | ------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter( IS NOT NULL) 4 - access("T2"."ID"=:B1) 6 - access("T2"."ID"=:B1) Note ----- - dynamic sampling used for this statement (level=2) 31 rows selected. Elapsed: 00:00:00.01 11:15:36 SYS@zkm(1)> rollback; Rollback complete. Elapsed: 00:00:00.00