【问题标题】:Performance issue using while loop in insert statement在插入语句中使用 while 循环的性能问题
【发布时间】:2018-04-22 08:31:27
【问题描述】:

我正在使用 try catch 块来捕获具有约束错误的数据。 例如。如果在非空列中插入 null 或插入重复记录或发生类型不匹配,则所有有错误的源记录都应转到错误日志表,其余记录应转到目标表。 为此我使用try catch,所以我不能使用批量插入,因此使用While Loop逐行插入,这需要永远运行,因为我必须插入3000000条记录。 有什么方法可以提高循环时的性能吗?所以它可以在最短的时间内插入 3000000 条记录?目前需要 2 小时或更长时间 :(

【问题讨论】:

  • 为什么要使用循环插入行?这是您表现不佳的主要问题。使用 try catch 创建一个存储过程,然后一次全部插入。我很确定即使它失败了,你也应该编写一些逻辑来处理非空列中是否出现空值
  • 我什至不确定您是否可以在开始 try/catch 语句中处理不同表的输出,因此它是基于事务的。所以我会想出一些逻辑来避免这个问题。你不能只写 ID 不为空的地方并插入到你的主表中。并写入 ID 为 null 的位置并插入到您的 error_log
  • 嗨,问题是如果其中有任何类型错误的任何记录应该移动到错误日志表,如果我只是使用 try catch 如果其中一条记录有错误,则整个批次将被移动要捕获在错误表中插入记录的部分,因此我必须测试每一行并相应地移动记录。寻找一种我可以使用一些快速方法的方法。谢谢你:)
  • 我不认为这种方法是合理的 IF 你事先知道什么情况会导致引发异常。 IF 您只能选择有效的(非空列的非空值)行仅将它们插入目标并使用数据集逻辑而不是 RBAR在日志中插入无效行> 程序性的
  • 使用适当的软件(MySQL、Oracle、DB2...)和版本标记数据库问题很有帮助,例如sql-server-2014。语法和功能的差异通常会影响答案。请注意,tsql 缩小了选择范围,但没有指定数据库。

标签: performance tsql query-tuning


【解决方案1】:

尝试分批进行插入。例如,做一个循环尝试一次插入 10,000/1,000/100 条记录作为批量插入。如果批处理中存在错误,则捕获它并作为逐行操作重新执行该批处理。您将不得不调整批量大小并使其足够小,以便将大多数批次作为批量插入处理,并且偶尔只需逐行处理一个批次。

【讨论】:

  • 在出现错误时立即切换到逐行的替代方法是将批次分成两半并重新处理它们,以通过二分搜索找到问题行。性能将取决于坏行的分布和初始批量大小。
【解决方案2】:

以下演示了在发生错误时对批量大小进行“二分查找”批量处理一堆样本数据。

set nocount on;

-- Set the processing parameters.
declare @InitialBatchSize as Int = 1024;
declare @BatchSize as Int = @InitialBatchSize;

-- Create some sample data with somewhat random   Divisor   values.
declare @RowsToProcess as Int = 10000;
declare @SampleData as Table ( Number Int, Divisor Int );
with Digits as ( select Digit from ( values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9) ) as Digits( Digit ) ),
  Numbers as (
  select ( ( ( Ten_4.Digit * 10 + Ten_3.Digit ) * 10 + Ten_2.Digit ) * 10 + Ten_1.Digit ) * 10 + Ten_0.Digit + 1 as Number
    from Digits as Ten_0 cross join Digits as Ten_1 cross join Digits as Ten_2 cross join
      Digits as Ten_3 cross join Digits as Ten_4 )
  insert into @SampleData
    select Number, Abs( Checksum( NewId() ) ) % 1000 as Divisor -- Adjust "1000" to vary the chances of a zero divisor.
      from Numbers
      where Number < @RowsToProcess;

-- Process the data.  
declare @FailedRows as Table ( Number Int, Divisor Int, ErrorMessage NVarChar(2048) );
declare @BitBucket as Table ( Number Int, Divisor Int, Quotient Int );
declare @RowCount as Int = 1; -- Force at least one loop execution.
declare @LastProcessedNumber as Int = 0;
while @RowCount > 0
  begin
  begin try
    -- Subject-to-failure   INSERT .
    insert into @BitBucket
      select top ( @BatchSize ) Number, Divisor, 1 / Divisor as Quotient
        from @SampleData
        where Number > @LastProcessedNumber
        order by Number;
    set @RowCount = @@RowCount;
    select @LastProcessedNumber = Max( Number ) from @BitBucket;
    print 'Processed ' + Cast( @RowCount as VarChar(10) ) + ' rows.';
  end try
  begin catch
    if @BatchSize > 1
      begin
      -- Try a smaller batch.
      set @BatchSize /= 2;
      end
    else
      begin
      -- This is a failing row.  Log it with the error and reset the batch size.
      set @LastProcessedNumber += 1;
      print 'Row failed. Row number ' + Cast( @LastProcessedNumber as VarChar(10) ) + ', error: ' + Error_Message() + '.';
      insert into @FailedRows
        select Number, Divisor, Error_Message()
          from @SampleData
          where Number = @LastProcessedNumber;
      set @BatchSize = @InitialBatchSize;
      end
  end catch
  end;

-- Dump the results.
select * from @FailedRows order by Number;
select * from @SampleData order by Number;
select * from @BitBucket order by Number;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-27
    • 1970-01-01
    • 2012-12-16
    • 1970-01-01
    • 2013-11-17
    • 2021-01-17
    • 2019-08-21
    相关资源
    最近更新 更多