如何从游标中获取,删除,提交

时间:2011-04-22 15:55:50

标签: sql oracle plsql

我试图从表中删除很多行。我想尝试将我想要删除的行放入游标,然后继续对游标的每一行进行获取,删除,提交,直到它为空。

在下面的代码中,我们是fetching行,并将它们放在TYPE中。

如何修改以下代码以从图片中删除TYPE,只需在光标本身上执行fetch,delete,commit即可。

    OPEN bulk_delete_dup;
    LOOP
        FETCH bulk_delete_dup BULK COLLECT INTO arr_inc_del LIMIT c_rows;

        FORALL i IN arr_inc_del.FIRST .. arr_inc_del.LAST
              DELETE FROM UIV_RESPONSE_INCOME 
              WHERE ROWID = arr_inc_del(i);

        COMMIT;
        arr_inc_del.DELETE;
        EXIT WHEN bulk_delete_dup%NOTFOUND;
    END LOOP;
    arr_inc_del.DELETE;
    CLOSE bulk_delete_dup;

2 个答案:

答案 0 :(得分:17)

为什么要分批提交?这只会减慢您的处理速度。除非有其他会话尝试修改您要删除的行,这似乎有其他原因造成问题,最有效的方法是简单地删除带有单个DELETE的数据,即

DELETE FROM uiv_response_income uri
 WHERE EXISTS( 
    SELECT 1
      FROM (<<bulk_delete_dup query>>) bdd
     WHERE bdd.rowid = uri.rowid
  )

当然,根据光标背后的查询设计方式,可能有一种更优化的方式。

如果你真的想要消除BULK COLLECT(这将大大减慢进程),你可以使用WHERE CURRENT OF语法来执行DELETE

SQL> create table foo
  2  as
  3  select level col1
  4    from dual
  5  connect by level < 10000;

Table created.

SQL> ed
Wrote file afiedt.buf

  1  declare
  2    cursor c1 is select * from foo for update;
  3    l_rowtype c1%rowtype;
  4  begin
  5    open c1;
  6    loop
  7      fetch c1 into l_rowtype;
  8      exit when c1%notfound;
  9      delete from foo where current of c1;
 10    end loop;
 11* end;
SQL> /

PL/SQL procedure successfully completed.

但请注意,由于必须锁定行(使用FOR UPDATE子句),因此无法在循环中提交提交。执行提交将释放您使用FOR UPDATE请求的锁定,并且您将获得ORA-01002:获取无序错误

SQL> ed
Wrote file afiedt.buf

  1  declare
  2    cursor c1 is select * from foo for update;
  3    l_rowtype c1%rowtype;
  4  begin
  5    open c1;
  6    loop
  7      fetch c1 into l_rowtype;
  8      exit when c1%notfound;
  9      delete from foo where current of c1;
 10      commit;
 11    end loop;
 12* end;
SQL> /
declare
*
ERROR at line 1:
ORA-01002: fetch out of sequence
ORA-06512: at line 7

如果删除锁定并避免使用WHERE CURRENT OF语法,则可能不会收到运行时错误,并根据从游标中提取的值删除数据。但是,这仍然是在提交中进行提取,这是一种糟糕的做法,并且从根本上会间歇地获得ORA-01555:快照太旧的错误,从根本上增加了几率。与单个SQL语句或BULK COLLECT选项相比,它也会非常缓慢。

SQL> ed
Wrote file afiedt.buf

  1  declare
  2    cursor c1 is select * from foo;
  3    l_rowtype c1%rowtype;
  4  begin
  5    open c1;
  6    loop
  7      fetch c1 into l_rowtype;
  8      exit when c1%notfound;
  9      delete from foo where col1 = l_rowtype.col1;
 10      commit;
 11    end loop;
 12* end;
SQL> /

PL/SQL procedure successfully completed.

当然,您还必须确保您的进程可以重新启动,以防您在进程终止之前处理某些行子集并具有一些未知数量的临时提交。如果DELETE足以导致不再从游标返回行,则您的进程可能已经可以重新启动。但总的来说,如果你试图将一个操作分解为多个事务,这就是一个问题。

答案 1 :(得分:1)

一些事情。从您公司的“8秒内无交易”规则(8秒,您在德克萨斯州?)看来,您有一个生产数据库实例,传统上支持执行OLTP操作的应用程序(插入1行,更新2行等),并且现在也成为批处理db(删除50%的行并替换为1mm的新行)。

批处理应与OLTP实例分开。在批处理(“数据工厂”)实例中,我不会尝试删除在这种情况下,我可能会做一个CTAS,删除旧表,重命名新表,重建索引/统计信息,重新编译无效的objs方法。

假设您在“8秒”实例中遇到批处理问题,您可能会发现您的公司将来会要求越来越多的这样做,所以请DBA尽可能多地回滚,并希望你不要通过提取提交来获得过快的快照(游标选择驱动删除,提交每1000行左右,使用rowid删除)。

如果DBA无法提供帮助,您可以先创建一个包含要删除的rowid的临时表,然后遍历临时表以从主表中删除(避免提交提交),但您的公司将可能有一些规则反对这个,这是另一种(基本)批处理技术。

类似的东西:

declare

-- assuming index on someCol
cursor sel_cur is
select rowid as row_id
from someTable
where someCol = 'BLAH';

v_ctr pls_integer := 0;
begin
for rec in sel_cur
loop
  v_ctr := v_ctr + 1;
  -- watch out for snapshot too old...
  delete from someTable
  where rowid = rec.row_id;
  if (mod(v_ctr, 1000) = 0) then
    commit;
  end if;
end loop;
commit;

end;