SQL优化问题(oracle)

时间:2009-12-03 17:14:50

标签: sql oracle optimization

编辑:请回答我问的两个答案中的一个。我知道还有其他选择在不同的情况下会更好。这些其他可能的选项(对表进行分区,作为一个大型删除语句运行,无需批量提交等)在我的情况下是 NOT 选项,因为我无法控制。

我有几个非常大的表要删除。所有都具有索引的相同外键。我需要从所有表中删除某些记录。

table source
  id --primary_key
  import_source --used for choosing the ids to delete

table t1
  id --foreign key
  --other fields

table t2
  id --foreign key
  --different other fields

通常在进行这样的删除时,我会整理一个循环以逐步执行所有ID:

declare
my_counter integer := 0;
begin
for cur in (
select id from source where import_source = 'bad.txt'
) loop
  begin
    delete from source where id = cur.id;
    delete from t1 where id = cur.id;
    delete from t2 where id = cur.id;
    my_counter := my_counter + 1;
    if my_counter > 500 then
      my_counter := 0;
      commit;
    end if;
    end;
  end loop;
  commit;
end;

然而,在我在其他地方看到的一些代码中,它被放在一个单独的循环中,每次删除一个。

declare
type import_ids is table of integer index by pls_integer;
my_count integer := 0;
begin
select id bulk collect into my_import_ids from source where import_source = 'bad.txt'

for h in 1..my_import_ids.count
  delete from t1 where id = my_import_ids(h);
    --do commit check
end loop;
for h in 1..my_import_ids.count
  delete from t2 where id = my_import_ids(h);
    --do commit check
end loop;

--do commit check will be replaced with the same chunk to commit every 500 rows as the above query

所以我需要回答以下问题之一:

1)哪一个更好?

2)我怎样才能找出哪个更适合我的具体案例? (IE,如果它取决于我有多少桌子,它们有多大,等等)

编辑:

由于这些表的大小,我必须在循环中执行此操作。我将从具有数亿条记录的表中删除数千条记录。这种情况发生在一个不能长时间锁定表的系统上。

编辑:

注意:我必需批量提交。数据量太大,无法在一批中执行。回滚表将使我们的数据库崩溃。

如果有一种方法可以批量提交而不是循环,我愿意听。否则,不要说我不应该使用循环...

6 个答案:

答案 0 :(得分:7)

为什么要循环?

delete from t1 where id IN (select id from source where import_source = 'bad.txt';
delete from t2 where id IN (select id from source where import_source = 'bad.txt';
delete from source where import_source = 'bad.txt'

那是使用标准SQL。我并不特别了解Oracle,但是许多DBMS还具有基于JOIN的多表DELETE,它可以让你在一个语句中完成整个过程。

答案 1 :(得分:6)

大卫 如果您坚持提交,可以使用以下代码:

declare
  type import_ids is table of integer index by pls_integer;
  my_import_ids import_ids;
  cursor c is select id from source where import_source = 'bad.txt';
begin
  open c;
  loop
    fetch c bulk collect into my_import_ids limit 500;
    forall h in 1..my_import_ids.count
      delete from t1 where id = my_import_ids(h);
    forall h in 1..my_import_ids.count
      delete from t2 where id = my_import_ids(h);
    commit;
    exit when c%notfound;
  end loop;
  close c;
end;

该程序以500行的形式获取ID,删除并提交每一行。它应该比逐行处理快得多,因为bulk collectforall作为单个操作(在与数据库的单次往返中),从而最小化上下文切换的数量。有关详细信息,请参阅Bulk Binds, Forall, Bulk Collect

答案 2 :(得分:1)

首先,你不应该在循环中commit - 它效率不高(生成大量重做),如果发生一些错误,你就无法回滚。

如前面的答案中所述,您应该发出单个delete,或者,如果要删除大多数记录,那么创建包含剩余行的新表,删除旧表并重命名可能更为理想。新的名字。

这样的事情:

CREATE TABLE new_table as select * from old_table where <filter only remaining rows>;

index new_table
grant on new table
add constraints on new_table
etc on new_table

drop table old_table
rename new_table to old_table;

另见Ask Tom

答案 3 :(得分:1)

Larry Lustig是对的,你不需要循环。尽管如此,在较小的块中进行删除可能会有一些好处。这里PL / SQL批量绑定可以大大提高速度:

declare
type import_ids is table of integer index by pls_integer;
my_count integer := 0;
begin
select id bulk collect into my_import_ids from source where import_source = 'bad.txt'

forall h in 1..my_import_ids.count
  delete from t1 where id = my_import_ids(h);
forall h in 1..my_import_ids.count
  delete from t2 where id = my_import_ids(h);

我写它的方式是一次性完成,在这种情况下,单个SQL是更好的。但是你可以改变循环条件将其分解成块。关键点是

  • 不要在每一行上提交。如果有的话,只提交N行。
  • 使用N块时,请勿在普通循环中运行删除。使用forall以批量绑定的形式运行删除,这要快得多。

除了提交的开销之外,原因是每次在PL / SQL代码中执行SQL语句时,它基本上都会进行上下文切换。批量绑定避免这种情况。

答案 4 :(得分:0)

您可以尝试分区以使用并行执行,而不仅仅是删除一个分区。 The Oracle documentation可能有助于设置它。在这种情况下,每个分区都会使用它自己的回滚段。

答案 5 :(得分:0)

如果您在删除t1 / t2之前从源中删除,则表明您没有参照完整性约束(否则您会收到错误,说明存在子记录)。

我要用ON DELETE CASCADE创建约束。然后一个简单的

DECLARE
  v_cnt NUMBER := 1;
BEGIN
  WHILE v_cnt > 0 LOOP
   DELETE FROM source WHERE import_source = 'bad.txt' and rownum < 5000;
   v_cnt := SQL%ROWCOUNT;
   COMMIT;
  END LOOP;
END;

子记录会自动删除。

如果你没有ON DELETE CASCADE,我会选择带有ON COMMIT DELETE ROWS的GLOBAL TEMPORARY TABLE

DECLARE
  v_cnt NUMBER := 1;
BEGIN
  WHILE v_cnt > 0 LOOP
   INSERT INTO temp (id)
   SELECT id FROM source WHERE import_source = 'bad.txt' and rownum < 5000;
   v_cnt := SQL%ROWCOUNT;
   DELETE FROM t1 WHERE id IN (SELECT id FROM temp);
   DELETE FROM t2 WHERE id IN (SELECT id FROM temp);
   DELETE FROM source WHERE id IN (SELECT id FROM temp);
   COMMIT;
  END LOOP;
END;

我也会选择DBA允许的最大块。 我希望每笔交易至少持续一分钟。更频繁的提交将是一种浪费。

  

这发生在一个系统上   不能把桌子锁起来   那么久。

Oracle不会锁定表,只会锁定行。我假设没有人会锁定你要删除的行(或者至少不会很长时间)。所以锁定不是问题。