2009-12-03 131 views
1

编辑:请回答我问的两个答案之一。我知道还有其他的选择,在另一种情况下会更好。这些其他潜在的选项(分区表,作为一个大批量删除语句不分批提交等)是不是选项在我的情况下,由于我的控制之外的事情。SQL优化问题(oracle)

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

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)如何找出哪个更适合我的特殊情况? (也就是说,如果这取决于我有多少表,他们是多么大等)

编辑:

必须这样做在一个循环中,由于这些表的大小。我将从具有数亿条记录的表中删除数千条记录。这发生在一个无法承受这么长时间表锁定的系统上。

编辑:

注:我需要分批提交。数据量太大,无法在一批中完成。回滚表将使我们的数据库崩溃。

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

+1

如果您觉得由于表的大尺寸而必须使用循环,那么您(和/或DBA)是否使用数据库引擎的分区功能来查看?这是分区帮助的“用例”之一。例如,如果您拥有10 TB的10 TB行表,那么删除分区(基于ID)比遍历数百万行更容易。 – JasDev 2009-12-03 17:43:01

+1

是的,我们已经考虑过这个。我们已经分割了我们的数据库。但是,t1和t2(etc)表可以通过几种不同的方式访问(基于id以外的字段)。因此,任何对它们的分割都会伤害整体。 我遗漏了很多不影响我的问题的细节,但确实意味着我们无法对t1,t2等进行分区。 – 2009-12-03 18:00:00

+1

您知道无论删除多少行,您都不会锁好桌子吧?如果您正在删除某个其他进程尝试更新的行,则只会出现锁争用,这似乎不太可能。如果有人试图更新您尝试删除的行,那么阻止该行显得非常合理。 – 2009-12-04 06:01:23

回答

6

大卫, 如果硬要commiting,您可以使用下面的代码:

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行件,删除和commiting每一块取的ID。它应该比逐行处理快得多,因为bulk collectforall可以作为单个操作(在往返数据库的单往返中),从而最大限度地减少了上下文切换的次数。有关详细信息,请参见Bulk Binds, Forall, Bulk Collect

+0

随着光标内的提交,我想知道是否最好将ORDER BY插入到光标select中以确保在提交之前从源表中读取所有值,希望也可以减少快照的机会旧错误。你会想要检查执行计划,以确保当然正在执行排序。 – 2009-12-04 07:52:07

+0

David Aldridge:嗯,我们是否需要删除“源”表中的行?我忽略了它。但ORDER BY什么?我们想要对行进行排序,以便我们逐块读取它们,而不是再次返回到上一个块,对吧?我认为,全面扫描访问将做到这一点。 – 2009-12-04 09:00:57

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,可以让您在单个语句中完成整个事情。

+0

+1和一个可乐打我几秒钟。 – 2009-12-03 17:25:05

+0

不可能。由于这些表格有多大,我必须*在循环中做到这一点,随时提交。 – 2009-12-03 17:28:54

+0

我不是甲骨文公司的人,所以我不能质疑你们的DBA的声明,但是我发现很难相信甲骨文没有提供某种设置来执行这些DELETE命令。对于主要的SQL数据库,您无法发布如此简单的SQL命令是很困难的。 – 2009-12-03 17:58:39

1

首先,你不应该在循环中使用commit--它不是有效的(会产生大量的重做),如果发生错误,你不能回滚。

正如前面的答案中提到的那样,您应该发出单个delete s,或者,如果您要删除大部分记录,那么使用剩余行创建新表可以更优化,删除旧表并删除旧表并重命名新表以旧名称。

事情是这样的:

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

+0

我*必须*提交一个循环。这是来自我们DBA的订单 - 如果我不这样做,回滚信息将会过大,并会导致我们的系统崩溃。 我正在删除一小部分记录,所以创建新的和删除是不可行的。 – 2009-12-03 17:30:49

+1

在循环提交会给你ORA-01555错误,而不是提到性能较差。您的DBA应该增加回滚段大小。 – Majkel 2009-12-03 17:42:11

+0

不可行。我希望这个删除操作需要几小时才能完成。我们无法将生产数据库上的表锁定很长时间。 – 2009-12-03 18:03:57

1

拉里·勒斯蒂格是正确的,你不需要一个循环。尽管如此,在以较小的块进行删除时可能会有一些好处。这里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语句时,它本质上都会执行上下文切换。批量绑定避免这一点。

+0

你的第一点:是的,我做了500行左右的排。 – 2009-12-03 18:01:26

+0

第二点:你能提供更多细节forall的工作原理吗?如何重新编写查询以使用forall,但仍然批量提交 – 2009-12-03 18:02:31

0

无论如何,您可以尝试使用分区来使用并行执行,而不仅仅是删除一个分区。 The Oracle documentation可能证明这是有用的。在这种情况下,每个分区都会使用它自己的回滚段。

+0

由于我的控制之外的考虑事项,对t1,t2等表进行分区不是一个选项。 – 2009-12-03 18:30:33

+1

那么,我回答这样的问题: 1.您的解决方案都适合您的情况。 2.您可以通过测试确定哪个更好。在模拟环境中做两件事,看看会发生什么。 – 2009-12-03 21:02:03

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

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不锁定表,只锁定行。我假设没有人会锁定你正在删除的行(或者至少不会很长)。所以锁定不是问题。