【发布时间】:2018-01-22 11:05:07
【问题描述】:
我必须使用 PL/SQL 从远程表中删除近 5 亿行。由于 UNDO 表空间无法处理该卷,因此删除以 1,000,000 的批次大小进行并提交。另外 - 为了减少硬解析,我使用以下语法使用绑定变量:
str := 'delete from table@link where id >= :x and id < :y';
execute immediate str using start_id, start_id+1000000
并且在每次调用之后,start_id 递增 1000000,直到 SQL%rowcount 返回 0(零)并达到 end_id(已知)。
但是进程得到ORA-0430如下:
ORA-04030: out of process memory when trying to allocate 16408 bytes (QERHJ hash-joi,QERHJ Bit vector)
ORA-04030: out of process memory when trying to allocate 41888 bytes (kxs-heap-c,temporary memory)
请注意,我已经在使用绑定变量,因此在第一次执行后没有硬解析。
一件事可能是目标 ID 的范围。假设前几行按升序排列,则 ID 为
100,000,000,000
200,000,000,000
50,000,000,000,000,000
50,000,000,000,011,111
在第二次迭代中,从 200,000,000,000 到 200,000,100,000 的 ID 将被删除。
但由于此范围内没有 ID,因此将需要近 50,000,000,000 次迭代才能到达下一行(50,000,000,000,000,000 / 1000000 = 50,000,000,000)。
当然 - 我总是可以检查目标的 ID 并选择正确的范围(比默认的 100 万大得多)。
但这不应该是进程耗尽内存的情况。
添加代码:
remote.sql:远程执行:
create table test1
(
id number(38) primary key
);
insert into test1 select level from dual connect by level < 1000000;
insert into test1 values ( 1000000000000 );
insert into test1 values ( 2000000000000 );
commit;
exec dbms_stats.gather_table_stats ( ownname => user, tabname => 'test1',
cascade => true, estimate_percent => 100 );
commit;
local.sql:
create or replace procedure batch_del
as
l_min_val integer;
l_max_val integer;
l_cnt integer;
l_cnt_dst integer;
l_begin integer;
l_end integer;
l_str varchar2(1000);
l_tot_cnt integer;
pragma autonomous_transaction;
begin
l_tot_cnt := 0;
l_str := ' select min(id), max(id), count(*) from test1@dst';
execute immediate l_str into l_min_val, l_max_val, l_cnt_dst;
dbms_output.put_line ( 'min: ' || l_min_val || ' max: ' || l_max_val
|| ' total : ' || l_cnt_dst );
l_begin := l_min_val;
while l_begin < l_max_val
loop
begin
l_end := l_begin + 100000;
delete from test1@dst where id >= l_begin and id < l_end;
l_cnt := SQL%ROWCOUNT;
dbms_output.put_line ( 'Rows Processed : ' || l_cnt );
l_tot_cnt := l_tot_cnt + l_cnt;
dbms_output.put_line ( 'Rows Processed So Far : ' || l_tot_cnt );
commit;
exception
when others then
dbms_output.put_line ( 'Error : ' || sqlcode );
end;
l_begin := l_begin + 100000;
end loop;
dbms_output.put_line ( 'Total : ' || l_tot_cnt );
end;
**所有本地实现**
drop table test1;
create table test1
(
id number(38) primary key
);
insert into test1 select level from dual connect by level < 1000000;
insert into test1 values ( 1000000000000 );
insert into test1 values ( 2000000000000 );
commit;
exec dbms_stats.gather_table_stats ( ownname => user, tabname => 'test1',
cascade => true, estimate_percent => 100 );
commit;
create or replace procedure batch_del
as
l_min_val integer;
l_max_val integer;
l_cnt integer;
l_begin integer;
l_tot_cnt integer;
pragma autonomous_transaction;
begin
l_tot_cnt := 0;
select min(id), max(id) into l_min_val, l_max_val from test1;
l_begin := l_min_val;
while l_begin < l_max_val
loop
begin
delete from test1 where id >= l_begin and id < l_begin + 10000;
l_cnt := SQL%ROWCOUNT;
dbms_output.put_line ( 'Rows Processed : ' || l_cnt );
l_tot_cnt := l_tot_cnt + l_cnt;
dbms_output.put_line ( 'Rows Processed So Far : ' || l_tot_cnt );
commit;
exception
when others then
dbms_output.put_line ( 'Error : ' || sqlcode );
end;
l_begin := l_begin + 10000;
end loop;
dbms_output.put_line ( 'Total : ' || l_tot_cnt );
end;
set timing on;
set serveroutput on size unli;
exec batch_del;
【问题讨论】:
-
您是否有不要删除的行?看起来不是 - 在这种情况下,为什么不截断表呢?
-
你保留的比例是多少?将它们复制到其他地方可能会更容易,截断然后将它们复制回来;或仅包含您想要保留的 CTAS。表是否分区?索引在那个键上?你能在远程端完成工作吗?
-
在这种情况下,您应该认真考虑使用
create table x as select ... from y创建一个只包含您想要的行的新表。然后删除旧的,重命名新的,恢复约束,索引等。删除十亿行不是一件好事。 -
我绝对希望尝试在远程数据库上执行此操作,而不是通过 dblink,以防万一这是您的问题的原因。
-
你不需要为此使用动态 sql - 你可以用静态 sql 做同样的事情 - 即
delete from table@link where id >= start_id and id < start_id+1000000;从解析的角度来看,这会更好,因为动态 sql 每次调用时都需要解析,而静态 sql 则不需要(有关详细信息,请参阅Tom Kyte's answer)。
标签: sql oracle performance plsql