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

Posted

技术标签:

【中文标题】如何从游标中获取、删除、提交【英文标题】:how to fetch, delete, commit from cursor 【发布时间】:2011-04-22 15:55:49 【问题描述】:

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

在下面的代码中,我们是fetching 行并将它们放在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;

【问题讨论】:

在尝试使用游标之前,我会确保基于集合的解决方案不是一个选项。与游标相比,基于集合的解决方案效率更高、代码更少且更易于理解……请问为什么标准删除在您的场景中不起作用? 我工作的地方有一项政策。单个事务的运行时间不应超过 8 秒。这就是为什么我不能简单地在一行 sql 中做到这一点。 所以这个有问题的策略的解决方案是将所有事务拆分为单行事务,并牺牲数据的一致性?? 确实是糟糕的政策。 【参考方案1】:

为什么要分批提交?那只会减慢您的处理速度。除非有其他会话试图修改您尝试删除的行,这似乎有其他原因,最有效的方法是简单地使用单个 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 语法来执行删除

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: fetch out of sequence 错误

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: snapshot too old 错误的几率。与单个 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 足以导致不再从游标返回该行,则您的进程可能已经可以重新启动。但一般来说,如果您尝试将单个操作分解为多个事务,这将是一个问题。

【讨论】:

我不能为此给予足够多的支持。碰巧我正在使用 current of 处理代码并不断获取 fetch out of sequence 错误。当我回到 SO 看看我是否有任何回应时,很高兴找到解决方案。谢谢 我就是这样做的,有两个 cmets - 1,如果可能的话,使用 ROWID 获取和删除,并尽可能不频繁地提交。如果其他一些 rouge 事务持有您尝试删除的行的时间超过您等待的 8 秒,您仍然可能最终等待。【参考方案2】:

一些事情。从贵公司的“8 秒内无事务”规则(8 秒,您在德克萨斯州?)看来,您有一个生产数据库实例,该实例传统上支持执行 OLTP 工作的应用程序(插入 1 行,更新 2 行等),并且有现在也成为批处理数据库(删除 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;

【讨论】:

以上是关于如何从游标中获取、删除、提交的主要内容,如果未能解决你的问题,请参考以下文章

在其他两个游标中找到游标时如何从表中删除

如何从 PLSQL Oracle 过程中删除数据或清空游标?

如何从游标中获取记录

如何从 sqlite 游标中获取列值?

jython zxJDBC:如何从游标中获取字典?

如何从数据库中获取 Graphql 中的分页游标?