Oracle 检查巨大的插入进度

Oracle Check Huge Insert Progress

是否有可能监控大量插入的进度? 假设我们有一个代码:

MERGE INTO huge_table ht
USING (
    SELECT column1, column2
    FROM another_huge_table 
) aht ON (ht.column1 = aht.column1)
WHEN NOT MATCHED THEN
INSERT (column1, column2)
VALUES (aht.column1, aht.column2);
COMMIT;

表可以包含数百万行,插入过程可以持续数小时。是否有可能在 Oracle 中监视 DML 进度?

简短回答:没有数据库引擎可以开箱即用。您需要了解,在开始执行之前,Oracle 并不知道将合并多少行。如评论中所述,一个选项是在 PLSQL 中将查询拆分为多个块,并使用日志记录方法来监控提交的行与花费的时间。但这比 运行 一个普通的合并语句要慢得多,我能想到为什么这样做是值得的。

不过,这种方法可以以某种方式帮助您。为此,我假设您的合并语句不是 运行ning 并行。 Oracle 提供 v$session_longops 字典视图来监视长时间操作,定义为可能需要 6 秒以上的操作。但是,为了操作,你必须了解执行计划中的每一步。

让我们做一个 PoC

表格

SQL> create table test.huge_table ( c1 number, c2 varchar2(40) , c3 varchar2(40) ) ;

Table created.

SQL> create table another_huge_table ( c1 number, c2 varchar2(40) , c3 varchar2(40) ) ;

Table created.

让我们在目标 table 中插入 5M 行,在源 table 中插入 10m 行,这样我们就可以发出 merge 来插入 5M 行。

SQL> declare
  2  begin
  3  for i in 1 .. 5000000
  4  loop
  5     insert into test.huge_table
  6     values
  7     ( i ,
  8      lpad(dbms_random.string('A',1),round(dbms_random.value(20,30)),dbms_random.string('A',1)),
  9      lpad(dbms_random.string('A',1),round(dbms_random.value(20,30)),dbms_random.string('A',1))
 10     );
 11  end loop;
 12  commit;
 13* end;
SQL> /

PL/SQL procedure successfully completed.

SQL> exec dbms_stats.gather_table_stats ( ownname => 'TEST' , tabname => 'HUGE_TABLE' , block_sample => true ) ;

PL/SQL procedure successfully completed.

SQL> declare
  2  begin
  3  for i in 1 .. 10000000
  4  loop
  5     insert into test.another_huge_table
  6     values
  7     ( i ,
  8      lpad(dbms_random.string('A',1),round(dbms_random.value(20,30)),dbms_random.string('A',1)),
  9      lpad(dbms_random.string('A',1),round(dbms_random.value(20,30)),dbms_random.string('A',1))
 10     );
 11     if i = 5000000
 12     then
 13             commit ;
 14     end if;
 15  end loop;
 16  commit;
 17* end;
SQL> /

PL/SQL procedure successfully completed.

SQL> exec dbms_stats.gather_table_stats ( ownname => 'TEST' , tabname => 'ANOTHER_HUGE_TABLE' , block_sample => true ) ;

PL/SQL procedure successfully completed.

让我们验证计划

SQL> explain plan for
merge into test.huge_table t
using (
    select c1,c2,c3 from test.another_huge_table
) s
ON (t.c1 = s.c1)
when not matched then
insert (c1, c2, c3)
values ( s.c1, s.c2, s.c3 );  2    3    4    5    6    7    8    9

Explained.

SQL> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 4265949913

------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name               | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT         |                    |    10M|  1096M|       | 67324   (1)| 00:00:06 |
|   1 |  MERGE                  | HUGE_TABLE         |       |       |       |            |          |
|   2 |   VIEW                  |                    |       |       |       |            |          |
|*  3 |    HASH JOIN RIGHT OUTER|                    |    10M|  1106M|   333M| 67324   (1)| 00:00:06 |
|   4 |     TABLE ACCESS FULL   | HUGE_TABLE         |  5000K|   276M|       |  8232   (1)| 00:00:01 |
|   5 |     TABLE ACCESS FULL   | ANOTHER_HUGE_TABLE |    10M|   553M|       | 16291   (1)| 00:00:02 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("T"."C1"(+)="C1")

然后我们运行合并

SQL> merge into test.huge_table t
using (
    select c1,c2,c3 from test.another_huge_table
) s
ON (t.c1 = s.c1)
when not matched then
insert (c1, c2, c3)
values ( s.c1, s.c2, s.c3 );  2    3    4    5    6    7    8

5000000 rows merged.

Elapsed: 00:00:36.39

SQL> commit ;

Commit complete.

Elapsed: 00:00:00.01

与此同时,过程 运行 宁,我可以获得有关步骤的信息

SQL> r
  1  select opname,
  2         target,
  3         round( ( sofar/totalwork ), 2 ) * 100 percentage_complete,
  4         start_time,
  5         ceil( time_remaining / 60 ) max_time_remaining_in_min,
  6         floor( elapsed_seconds / 60 ) time_spent_in_min
  7         from v$session_longops
  8*        where sofar != totalwork

OPNAME     TARGET          PERCENTAGE_COMPLETE START_TIM MAX_TIME_REMAINING_IN_MIN TIME_SPENT_IN_MIN
------------------- --------- ---------------------------------------------------
Table Scan TEST.ANOTHER_HUGE_TABLE          86 28-OCT-21                        1                 0


OPNAME     TARGET          PERCENTAGE_COMPLETE START_TIM MAX_TIME_REMAINING_IN_MIN TIME_SPENT_IN_MIN
------------------- --------- ---------------------------------------------------
Hash Join TEST.ANOTHER_HUGE_TABLE          42 28-OCT-21                        1                 0

OPNAME     TARGET          PERCENTAGE_COMPLETE START_TIM MAX_TIME_REMAINING_IN_MIN TIME_SPENT_IN_MIN
------------------- --------- ---------------------------------------------------
Hash Join TEST.ANOTHER_HUGE_TABLE          77 28-OCT-21                        1                 0

总结

如果 Oracle 认为这些步骤中的任何一个将花费超过 6 秒,它会将它们包括在 v$session_longops 中。您可以查询此 viev 以获得每个步骤的估计值,但它并不是整个 DML 将花费多少时间的真实度量。但这可能是一个好的开始。

另一种可能更好的方法是生成基线。您可以在 PLSQL 或任何其他语言中包含合并,包括用于收集开始时间、结束时间和合并行的日志记录方法。随着时间的推移和数十次执行,您可以应用统计数据并获得平均 and/or 标准偏差。我正在使用类似的方法来生成这种类型的信息,我可以用它来进行有根据的猜测,仅此而已。

希望这对你有所帮助。