【问题标题】:Why successive deletes are taking increased time?为什么连续删除需要增加时间?
【发布时间】:2015-12-01 03:12:01
【问题描述】:

我有一张表(比如tableB),其中有大约 4000 万行(一直在增加)。归档目前由DELETE .. OUTPUT .. INTO .. FROM .. 方式完成。归档 1000 行需要 3~5 秒 最初。但是随着越来越多的行被删除,它需要更多的时间。例如,删除 10M 行后,现在需要 35~40 秒 删除 1000 行。

这是什么原因造成的? 我该如何改善这种情况(我需要归档至少 30M 行)? 如果分区是唯一的方法,我怎样才能以最少的停机时间做到这一点?

其他信息:

  • tableB 有 2 个外键列(tableAIdtableCId
  • 正在根据tableA 中的日期时间字段执行存档(inner-join 正在DELETE 语句中使用)
  • 查询计划显示内部连接的成本为 81%
  • 如果我对tableA 使用查询提示with (index=ix_time),则计划显示tableB 的外键列上的索引的索引查找成本为100%
  • tableAtableB 都具有自动递增的 bigint 作为主键。
  • tableB 有 4 个索引
  • tableA 有 5 个索引
  • tableA 有 30M+ 行。
  • 每天都在重建/重组/更新指数/统计数据
  • SQL Server 2008R2
  • Windows Server 2008R2,16 核,32G RAM
  • 我是一个偶然的dba atm

查询计划摘录:

脚本:

DECLARE @older_than datetime2(0) = '2015-10-01';
DECLARE @i int = 1;
DECLARE @j int = 0;
DECLARE @imax int = 1000;
DECLARE @jmax int = 50;
DECLARE @total int = 0;
DECLARE @t1 DATETIME2(3);
DECLARE @t2 DATETIME2(3);
DECLARE @timetook int;

WHILE @i > 0 AND @j < @jmax
BEGIN
    SET @t1 = GETDATE();
    DELETE TOP (@imax) ss   
        OUTPUT  deleted.[Id]    
          ,deleted.[columnA]
          ,deleted.[columnB]          

        INTO [MyArchive_Data].dbo.tableB([Id]
          ,columnA
          ,columnB)

    FROM [MyLive_Data].dbo.tableB ss
    INNER JOIN [MyLive_Data].dbo.tableA s ON s.Id = ss.tableAID

    WHERE s.Time < @older_than;
    SET @i = @@rowcount;
    SET @j = @j + 1;
    SET @total = @total + @i;
    SET @t2 = GETDATE();
    SET @timetook = datediff(second,@t1,@t2);
    RAISERROR('LOOP %d COMPLETE [%d rows][%d sec]',10,1,@j, @total, @timetook) with nowait;
    WAITFOR DELAY '00:00:03';
END

更新

如果我排除循环构造(WHILE @i &gt; 0 AND @j &lt; @jmax)并仅单独运行 DELETE 语句,似乎需要 10~12 秒。我观察了查询计划。那些是不同的。循环结构打开时,它使用index-seek,但没有它,则使用index-scan。为什么?

【问题讨论】:

  • 你能发表你的DELETE声明吗?
  • 在执行删除操作时尝试禁用索引,因为索引会降低删除性能,因为 SQL 服务器必须在每次删除后更新索引。
  • @Felix Pamittan:已发布脚本
  • 您使用的是TOP,而没有ORDER BY。这是故意的吗?
  • 除了您的时间标准之外,考虑按 ID 范围删除,而不是使用 TOP 删除。我希望这会提供搜索而不是扫描。

标签: sql-server database database-administration


【解决方案1】:

如果没有看到带有索引的完整表架构,我会说 SQL 服务器查询优化器发现扫描聚集索引更有效,因为表上的统计信息显示了 ID 值和知道时间之间的相关性如果它从聚集索引的末尾开始并以相反的顺序处理它,它将必须读取更少的行来找到满足查询的 TOP(x) 行。 Brent Ozar's post on Scans, Seeks and Statistics

此外,循环中有WAITFOR 语句,这可能会导致表上的锁升级,因为删除是在隐式事务中完成的,因此只有在循环结束后才会提交删除。尝试在删除语句之前添加BEGIN TRANSACTION,并在其后添加COMMIT TRANSACTION。如果可能,请删除 WAITFOR 语句,因为它会导致处理延迟。

DECLARE @older_than datetime2(0) = '2015-10-01';
DECLARE @i int = 1;
DECLARE @j int = 0;
DECLARE @imax int = 1000;
DECLARE @jmax int = 50;
DECLARE @total int = 0;
DECLARE @t1 DATETIME2(3);
DECLARE @t2 DATETIME2(3);
DECLARE @timetook int;

WHILE @i > 0 AND @j < @jmax
BEGIN
SET @t1 = GETDATE();
BEGIN TRANSACTION 
DELETE TOP (@imax) ss   
    OUTPUT  deleted.[Id]    
      ,deleted.[columnA]
      ,deleted.[columnB]          

    INTO [MyArchive_Data].dbo.tableB([Id]
      ,columnA
      ,columnB)

FROM [MyLive_Data].dbo.tableB ss
INNER JOIN [MyLive_Data].dbo.tableA s ON s.Id = ss.tableAID

WHERE s.Time < @older_than;
COMMIT TRANSACTION 
SET @i = @@rowcount;
SET @j = @j + 1;
SET @total = @total + @i;
SET @t2 = GETDATE();
SET @timetook = datediff(second,@t1,@t2);
RAISERROR('LOOP %d COMPLETE [%d rows][%d sec]',10,1,@j, @total, @timetook) with nowait;
WAITFOR DELAY '00:00:03';
END

【讨论】:

    【解决方案2】:

    下面是一个示例,我希望它对于非常大的存档处理会更有效。此方法是按 ID 范围而不是 TOP 批量删除。您可以根据性能和并发需求调整批量大小。

    DECLARE
          @older_than datetime2(0) = '2015-10-01'
        , @i int = 1
        , @j int = 0
        , @total int = 0
        , @t1 DATETIME2(3)
        , @t2 DATETIME2(3)
        , @timetook int
        , @MinID int
        , @MaxId int
        , @BatchFirstId int
        , @BatchLastId int
        , @BatchSize int =100000;
    SELECT @MinID = MIN(Id), @MaxID = MAX(Id) FROM dbo.TableA;
    SET @BatchFirstId = @MinID;
    WHILE @BatchFirstId <=  @MaxId
    BEGIN
    
        SET @BatchLastID = @BatchFirstId + @BatchSize - 1;
        SET @t1 = GETDATE();
        DELETE ss   
            OUTPUT  deleted.[Id]    
              ,deleted.[columnA]
              ,deleted.[columnB]          
    
            INTO [MyArchive_Data].dbo.tableB([Id]
              ,columnA
              ,columnB)
    
        FROM [MyLive_Data].dbo.tableB ss
        INNER JOIN [MyLive_Data].dbo.tableA s ON s.Id = ss.tableAID
        WHERE s.Time < @older_than
            AND s.Id BETWEEN @BatchFirstID AND @BatchLastID;
    
        SET @i = @@ROWCOUNT;
        SET @BatchFirstId += @BatchSize;
        SET @j = @j + 1;
        SET @total = @total + @i;
        SET @t2 = GETDATE();
        SET @timetook = datediff(second,@t1,@t2);
        RAISERROR('LOOP %d COMPLETE [%d rows][%d sec]',10,1,@j, @total, @timetook) with nowait;
        WAITFOR DELAY '00:00:03';
    
    END;
    

    【讨论】:

      猜你喜欢
      • 2010-09-14
      • 1970-01-01
      • 1970-01-01
      • 2018-02-19
      • 2010-09-28
      • 1970-01-01
      • 1970-01-01
      • 2014-05-29
      • 2023-03-25
      相关资源
      最近更新 更多