【问题标题】:How to delete faster?如何更快地删除?
【发布时间】:2010-11-11 16:49:57
【问题描述】:

我有一个包含 25 亿条记录的数据库表。 有 1100 万个重复项。 删除这 1100 万条记录的最快方法是什么?

【问题讨论】:

  • 只是为了查看系统性能,获取重复行数的查询耗时 1 小时 40 分钟。
  • 我认为 OP 删除了他的帐户。
  • 谢谢大家!我必须将唯一记录复制到表中,截断原始表并复制回唯一数据。

标签: database performance oracle plsql


【解决方案1】:

从多条记录中删除一个重复项是一件棘手的事情,而且有这么多记录,您就有问题了。

一种选择是解决问题,将要保留的记录复制到新表中。您可以使用CREATE TABLE AS SELECT DISTINCT ... NOLOGGING 语法,它会在不使用事务日志的情况下复制您的重复数据删除记录,这样会快得多。填充新表后,删除/重命名旧表,然后将新表重命名到位。

http://www.databasejournal.com/features/oracle/article.php/3631361/Managing-Tables-Logging-versus-Nologging.htm

哦,记得在新表上添加一个 UNIQUE 索引,这样就不会再发生这种情况了。

这个故事的寓意是...... 永远不要使用 DELETE 来删除大量记录,它非常慢,因为它必须将所有已删除的记录存储在重做日志中。复制和切换,或 TRUNCATE。

【讨论】:

  • ...您可以将相同的算法应用于生产产品的团队,该产品仅允许 11,000,000 行重复;-) Keith。
  • 这个答案+1。我肯定很想创建表格的新副本并插入其中。我要补充的关键是,在您复制数据之前不要在该辅助表上放置任何索引 - 您不希望在插入数据时必须保持索引保持原样而造成不必要的打击。我也喜欢这种方法,因为它有一个额外的安全网 - 在你 100% 确定你已经获得了所有正确的数据之前,你不必摆脱旧表。
  • 使用相同的谓词比较复制 24.89 亿条记录与删除 1100 万条记录所需的时间会很有趣
  • @skaffman,如果“它有效”是指它更快,那么引用“正在运行”会降低您的案例,因为(您应该知道)查询性能“在action" 不仅取决于查询,而且会根据服务器的状态(以前的查询、缓存的数据等)而有很大差异。因此,如果没有广泛的指标和限定符来消除这些其他因素的影响,那么您所呈现的一切是主观印象...
  • 我不同意或修改你的道德“永远不要使用 DELETE 来删除大量记录”,而不定义什么是“大”。如果只是表中总数据的 0.5%,那么 1100 万可能并不大,就像我们在这里看到的那样。如果不了解有关表结构和需要执行删除的条件的更多信息,就无法判断。请参阅我的答案以进行详细说明。
【解决方案2】:
DELETE
FROM    mytable
WHERE   rowid IN
        (
        SELECT  rowid
        FROM    (
                SELECT  rowid, ROW_NUMBER() OVER (ORDER BY dupfield) rn
                FROM    mytable r
                )
        WHERE   rn > 1
        )

或者甚至是这样:

DELETE
FROM    mytable mo
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    mytable mi
        WHERE   mi.dup_field = mo.dup_field
                AND mi.rowid <> mo.rowid
        )

这两个查询都将使用相当高效的HASH SEMI JOIN,如果dup_field 上没有索引,后者会更快。

您可能想复制行,但请注意,复制2G 行时生成的REDOUNDO 信息比删除11M 时要多得多。

【讨论】:

  • 表大小为25亿时,这样的更新性能如何?
  • 我感觉这个查询很慢,但可以达到OP的需要。这可以重写为连接吗?
  • 将在dupfield 上进行排序(如果上面没有索引),这可能需要很长时间。 rowid 上的联接将是 HASH SEMI JOIN,这在 2G 上与 11M 行之间只需几分钟。自己删除也要几十分钟,主要是生成REDOUNDO
  • @Manuel: Oracle 足够聪明,可以将其重写为更高效的HASH SEMI JOIN
【解决方案3】:

是否删除现有行或创建适当的新表并删除旧表更快取决于很多因素。 1100 万行很多,但仅占表中总行数的 0.5%。重新创建和删除很可能比删除慢得多,这取决于源表上存在多少索引,以及需要删除的行在数据页上的位置。

然后是源表是否处于活动状态的问题。如果在进行此清理时正在进行插入和更新,则如果没有大量额外代码在事后同步表,则复制和删除将无法工作。

最后,为什么这个操作必须“快”?是因为在进程发生时系统需要离线吗?您可以编写一个在系统处于活动状态时删除欺骗的过程,但不会影响系统的其余部分来使用撤消。过去我们已经解决了这个问题,首先编写一个查询来收集第二个表中要删除的行的主键,如下所示:

  INSERT
    INTO RowsToDeleteTable
  SELECT PKColumn
    FROM SourceTable
   WHERE <conditions used to find rows to remove>

CREATE UNIQUE INDEX PK_RowsToDelete ON RowsToDeleteTable (PKColumn);

然后我们有一个 PL/SQL 块,它可以像这样循环游标中的行:

BEGIN
  FOR theRow IN (SELECT PKColumn FROM RowsToDeleteTable ORDER BY 1) LOOP
    <delete source table for theRow.PKColumn)
    <optionally wait a bit>
    commit;
  END LOOP;
END;

或者做这样的事情:

BEGIN
  FOR theRow IN (SELECT MIN(PKColumn) FROM RowsToDeleteTable ) LOOP
    <delete source table for theRow.PKColumn)
    <optionally wait a bit>
    DELETE RowsToDeleteTable
     WHERE PKColumn = theRow.PKColumn;
    commit;
  END LOOP;
END;

循环和“SELECT MAX”显然效率较低,但它的优点是可以让您跟踪删除操作的进度。我们在循环中放置了一些等待代码,以允许我们控制收割操作发生的剧烈程度。

RowsToDeleteTable 的初始创建过程非常迅速,您的优势是可以让该过程随心所欲地进行。在这种情况下,删除在您的范围中留下的“洞”不会太糟糕,因为您删除的是总数据的一小部分。

【讨论】:

    【解决方案4】:

    首先在定义和包含重复值的列上放置一个索引,

    那么,假设表有一个主键(PK),

      Delete Table T Where PK <> 
            (Select Min(PK) From Table
             Where ColA = T.ColA
               ...  for each column in set defined above
               And ColB = T.ColB)
    

    注意:也可以使用 Max(PK),您所做的只是识别一条记录,不要从每组重复项中删除

    编辑:为了消除事务日志和 UNDO 分区的广泛使用,您可以将重复值存储在临时表中,然后在单个事务中删除每对的重复值...

    假设只有一列(称为 ColA,一个数字)定义了欺骗......

       Create Table Dupes (ColA Number)
       Insert Dupes(ColA)
       Select Distinct ColA
       From Table
       Group By ColA
       Having Count(*) > 1
    
       recordExists Number := 0 ;
       ColAValue Number;
       Select Case When Exists (Select Count(*) From Dupes)
       Then 1 Else 0 End Into recordExists From Dual;
    
    
       While recordExists = 1 
          Loop 
             Select (Select Max(ColA) From Dupes) 
             Into ColAValue From Dual;
             Begin Transaction
                Delete Table T
                Where ColA = ColAValue
                   And pk <> (Select Min(Pk) From Table 
                              Where ColA = ColAValue);
                Delete Dupes Where ColA = ColAValue;
             Commit Transaction;
             Select Case When Exists (Select Count(*) From Dupes)
             Then 1 Else 0 End Into recordExists From Dual;
          End Loop;
    

    未测试,因此语法可能需要按摩...

    【讨论】:

      【解决方案5】:

      如果您确定不会更改数据的完整性(参照完整性),请禁用约束(索引、其他约束),执行删除,然后启用约束。您必须先尝试一下,看看启用时刷新索引是否比启用索引时更耗时。

      一些查询优化可能也有帮助,但在不了解更多细节的情况下,我们只是在理论上讨论。

      【讨论】:

      • 不要删除用于查找重复项的列上的索引,对 2,500,000,000 行重复进行全表扫描会非常非常慢。
      • 它不会做重复的表扫描,如果没有索引它会做散列半连接。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-02-03
      • 1970-01-01
      • 2021-09-17
      • 2016-07-19
      • 2016-07-04
      • 2021-01-29
      • 1970-01-01
      相关资源
      最近更新 更多