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 存储过程光标运行缓慢的主要内容,如果未能解决你的问题,请参考以下文章