1. 程式人生 > 實用技巧 >Oracle Update語句中多表關聯中被關聯表多次全表掃描問題

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<=
100; create table t2 as select * from dba_objects where rownum<=50;
View Code
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 where
rownum<=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