ORACLE 存储过程光标运行缓慢

Posted

技术标签:

【中文标题】ORACLE 存储过程光标运行缓慢【英文标题】:ORACLE STORED PROCEDURE CURSOR IS WORKING VERY SLOW 【发布时间】:2019-03-18 08:26:17 【问题描述】:

我的一个存储过程最近花了大约 6 个小时,通常需要大约 3 个小时才能完成。 在检查时,我发现光标正在花时间执行。 这两个表都存在于我的本地数据库实例中。

我需要知道这可能是什么原因以及如何微调程序。

我的存储过程:

create or replace PROCEDURE VMS_DETAILS_D_1 IS
LOG_D1 VARCHAR2(20);
BEGIN

/* IDENTIFY PARTITION */

SELECT partition_name into LOG_D1 FROM all_tab_partitions a WHERE table_name = 'LOG' AND TABLE_OWNER='OWNER1' and partition_position IN 
(SELECT MAX (partition_position-1) FROM all_tab_partitions b WHERE table_name = a.table_name AND a.table_owner = b.table_owner);

execute immediate 'DROP TABLE TAB1 PURGE';
COMMIT;

EXECUTE IMMEDIATE 'create table TAB1 Nologging as
select /*+ Parallel(20) */  TRANSACTIONID,TIME_STAMP from OWNER1.log partition('||LOG_D1||') 
where ( MESSAGE = ''WalletUpdate| Request for Estel Update is Processed'' or MESSAGE = ''Voucher Core request processed'')';

EXECUTE IMMEDIATE 'CREATE INDEX IDX_TAB1 on TAB1(TRANSACTIONID)';

DBMS_STATS.GATHER_TABLE_STATS (ownname => 'OWNER2' , tabname => 'TAB1',cascade => true, estimate_percent => 10,method_opt=>'for all indexed columns size 1', granularity => 'ALL', degree => 1);


DECLARE
   CURSOR resp_cur
   IS
        select TRANSACTIONID,to_char(max(TIME_STAMP),'DD-MM-YYYY HH24:MI:SS') TIME_STAMP from TAB1 
        where TRANSACTIONID in (select ORDERREFNUM from TAB2
        where ORDERREFNUM like 'BV%') group by TRANSACTIONID;
BEGIN
   FOR l IN resp_cur
   LOOP
      update TAB2 
      set TCTIME=l.TIME_STAMP 
      where ORDERREFNUM=l.TRANSACTIONID;
      COMMIT;
   END LOOP;
END;

end; 

【问题讨论】:

这张表OWNER1.log包含多少数据?,还有它的不良建模​​对列消息有条件,可能有些字母会有大写或小写。 “我需要知道这可能是什么原因” 那么你的系统在最后一次运行和上一次运行之间发生了什么变化(当它显然是在一个可接受的时间范围)?我们无法回答这个问题。确实,这可能是局部时间异常,下次运行此代码时会没事的。 另外,你怎么知道是光标花了时间?该程序是否有您在此处发布之前已编辑的日志记录代码? @moudiz - “对列消息有条件的建模不好” 是吗?总是?如果消息是由标准消息列表中的批处理例程分配的(例如硬编码在例程中或存储在表中),则不会。 【参考方案1】:

首先,DDL 具有隐式提交,因此您不需要在放置表之后进行提交。 其次,为什么要删除表格并重新创建它,而不是截断表格并插入其中? 第三,当您可以在单个更新语句中执行更新时,为什么要循环游标来执行更新?

如果您绝对必须将数据存储在单独的表中,我会像这样重写您的过程:

CREATE OR REPLACE PROCEDURE vms_details_d_1 IS
  log_d1 VARCHAR2(20);
BEGIN

  /* IDENTIFY PARTITION */

  SELECT partition_name
  INTO   log_d1
  FROM   all_tab_partitions a
  WHERE  table_name = 'LOG'
  AND    table_owner = 'OWNER1'
  AND    partition_position IN (SELECT MAX(partition_position - 1)
                                FROM   all_tab_partitions b
                                WHERE  table_name = a.table_name
                                AND    a.table_owner = b.table_owner);

  EXECUTE IMMEDIATE 'TRUNCATE TABLE TAB1 reuse storage';

  EXECUTE IMMEDIATE 'insert into TAB1 (transactionid, time_stamp)'||CHR(10)||
                    'select /*+ Parallel(20) */  TRANSACTIONID,TIME_STAMP from OWNER1.log partition(' || log_d1 || ')'||CHR(10)||
                    'where MESSAGE in (''WalletUpdate| Request for Estel Update is Processed'', ''Voucher Core request processed'')';

  EXECUTE IMMEDIATE 'CREATE INDEX IDX_TAB1 on TAB1(TRANSACTIONID)';

  dbms_stats.gather_table_stats(ownname          => 'OWNER2',
                                tabname          => 'TAB1',
                                cascade          => TRUE,
                                estimate_percent => 10,
                                method_opt       => 'for all indexed columns size 1',
                                granularity      => 'ALL',
                                degree           => 1);

  MERGE INTO tab2 tgt
    USING (SELECT transactionid,
                  max(time_stamp) ts
           FROM   tab1
           GROUP BY transactionid) src
      ON (tgt.transactionid = src.transactionid)
  WHEN MATCHED THEN
    UPDATE SET tgt.tctime = to_char(src.ts, 'dd-mm-yyyy hh24:mi:ss'); -- is tab2.tctime really a string? If it's a date, remove the to_char

  COMMIT;
END vms_details_d_1;
/

如果您只是为了更轻松地进行更新而复制数据,则不需要 - 相反,您可以在单个 DML 语句中完成所有操作,如下所示:

CREATE OR REPLACE PROCEDURE vms_details_d_1 IS
  log_d1 VARCHAR2(20);
BEGIN

  /* IDENTIFY PARTITION */

  SELECT partition_name
  INTO   log_d1
  FROM   all_tab_partitions a
  WHERE  table_name = 'LOG'
  AND    table_owner = 'OWNER1'
  AND    partition_position IN (SELECT MAX(partition_position - 1)
                                FROM   all_tab_partitions b
                                WHERE  table_name = a.table_name
                                AND    a.table_owner = b.table_owner);

  EXECUTE IMMEDIATE 'MERGE INTO tab2 tgt'||CHR(10)||
                    '  USING (SELECT transactionid,'||CHR(10)||
                    '                MAX(time_stamp) ts'||CHR(10)||
                    '         FROM   owner1.log partition(' || log_d1 || ')'||CHR(10)||
                    '         GROUP BY transactionid) src'||CHR(10)||
                    '    ON (tgt.transactionid = src.transactionid)'||CHR(10)||
                    'WHEN MATCHED THEN'||CHR(10)||
                    '  UPDATE SET tgt.tctime = to_char(src.ts, ''dd-mm-yyyy hh24:mi:ss'')'; -- is tab2.tctime really a string? If it's a date, remove the to_char

  COMMIT;
END vms_details_d_1;
/

如果您知道定义您所追求的分区的谓词,则可以在查询中使用这些谓词,从而无需查找分区名称,因此需要动态 SQL。

【讨论】:

我认为在截断表时包含 REUSE STORAGE 子句是个好主意。假设每个月的数据量大体一致,那么在扩展区管理上的节省通常值得冒险,因为有时我们分配了未使用的空间。【参考方案2】:

好的,您的程序需要大量改进:

在下面的查询中,您可以使用user_tab_partitions 而不是all_tab_partitions

选择分区名称 进入 LOG_D1 FROM all_tab_partitions 一个 WHERE table_name = 'LOG' AND TABLE_OWNER = 'OWNER1' 和 partition_position IN (SELECT MAX(partition_position - 1) FROM all_tab_partitions b WHERE table_name = a.table_name AND a.table_owner = b.table_owner);

您必须对表 tab1 进行检查,以防它不存在且无需在此处提交,它不是 DML 语句。

立即执行 'DROP TABLE TAB1 PURGE'; 犯罪;

不需要更新过程中的统计信息,特别是它是一个新创建的表并且已经创建了索引,并且它只有一个索引。

以上内容可能会稍微提高性能,但您必须检查表日志中是否有列消息的索引(但正如我所说的错误建模)如果需要索引,还要检查 tab2 上的查询计划。

【讨论】:

" 您可以使用user_tab_partitions" 问题中没有任何内容表明此操作由 OWNER1 运行。它可能是OWNER2,甚至是第三方。即使它由 OWNER1 运行,这将如何提高性能? “无需更新程序中的统计数据” 完全不同意。在初始人口之后立即是收集(或修复)统计数据的最重要时间。 @APC 至于第一点,我同意不同的用户,但是从 ALL_ 中选择你将使用全表扫描访问所有数据库对象,即使涉及到条件。正如我所说的它可能会稍微提高性能,至于第二点,请更正我的信息,1-他创建了一个表,2)他插入了数据,他在特定列上创建了一个索引,所以这个索引的统计信息都是最新的,那么为什么他需要更新表上的统计信息尤其是没有索引。 Update statistics after creation of index【参考方案3】:

这是错误的方法,你正在做的是更新TAB2游标resp_cur中记录的次数,我会切换到合并。

【讨论】:

以上是关于ORACLE 存储过程光标运行缓慢的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 2008 R2 中运行缓慢的存储过程

存储过程在第一次运行时运行缓慢

Oracle SQL Developer调试Oracle存储过程

oracle 存储过程调用

Oracle SQL 查询运行缓慢

SYS_REFCURSOR out 参数 - 运行存储过程失败