【问题标题】:Batch commit on large INSERT operation in native SQL?在本机 SQL 中批量提交大型 INSERT 操作?
【发布时间】:2010-12-08 19:17:32
【问题描述】:

我有几个大表(188m 和 144m 行)我需要从视图中填充,但每个视图都包含几亿行(将伪维度建模数据组合成一个平面形式)。每个表上的键是超过 50 个复合字节的列。如果数据在表中,我总是可以考虑使用 sp_rename 来创建另一个新表,但这不是一个真正的选择。

如果我执行单个 INSERT 操作,该过程会占用大量事务日志空间,通常会将其归档并引发 DBA 的一堆麻烦。 (是的,这可能是 DBA 应该处理/设计/架构师的工作)

我可以使用 SSIS 并通过批量提交将数据流式传输到目标表中(但这确实需要通过网络传输数据,因为我们不允许在服务器上运行 SSIS 包)。

除了使用某种键将流程分成多个 INSERT 操作以将行分配到不同的批次并执行循环之外,还有什么其他的吗?

【问题讨论】:

  • 如果表分区是一个选项,您可以将插入除以分区值。可能会更快地将结果子集拼接在一起。

标签: sql sql-server tsql batch-processing


【解决方案1】:

视图是否有任何类型的唯一标识符/候选键?如果是这样,您可以使用以下方法将这些行选择到工作表中:

SELECT key_columns INTO dbo.temp FROM dbo.HugeView;

(如果有意义,可以将此表放入不同的数据库中,可能使用 SIMPLE 恢复模型,以防止日志活动干扰您的主数据库。无论如何,这应该会生成更少的日志,并且您可以释放恢复之前在另一个数据库中的空间,以防问题是您周围的磁盘空间不足。)

然后你可以这样做,一次插入 10,000 行,并在其间备份日志:

SET NOCOUNT ON;

DECLARE
    @batchsize INT,
    @ctr INT,
    @rc INT;

SELECT
    @batchsize = 10000,
    @ctr = 0;

WHILE 1 = 1
BEGIN
    WITH x AS
    (
        SELECT key_column, rn = ROW_NUMBER() OVER (ORDER BY key_column)
        FROM dbo.temp
    )
    INSERT dbo.PrimaryTable(a, b, c, etc.)
        SELECT v.a, v.b, v.c, etc.
        FROM x
        INNER JOIN dbo.HugeView AS v
        ON v.key_column = x.key_column
        WHERE x.rn > @batchsize * @ctr
        AND x.rn <= @batchsize * (@ctr + 1);

    IF @@ROWCOUNT = 0
        BREAK;

    BACKUP LOG PrimaryDB TO DISK = 'C:\db.bak' WITH INIT;

    SET @ctr = @ctr + 1;
END

这完全不是我的想法,所以不要剪切/粘贴/运行,但我认为总体思路就在那里。有关更多详细信息(以及为什么我在循环内备份日志/检查点),请参阅sqlperformance.com 上的这篇文章:

请注意,如果您要进行常规数据库和日志备份,您可能需要完整备份以重新启动您的日志链。

【讨论】:

  • 这很有趣,因为在我在服务器中构建大部分系统并仍在使用我的盒子进行开发之前,我已经为恢复目标划分了表的类别并将它们放在单独的模式中希望 DBA 能给我多个数据库,但不是……
  • 不幸的是key都是50多字节的复合列,猜猜是时候搞定了……
  • @AaronBertrand 我知道这是旧的,但我刚刚在测试中使用它。该理论工作正常,您只是在循环中缺少SET @ctr = @ctr + 1 以增加批号计数器。我希望你不介意 - 我已经编辑了它(在测试结果之后)。
  • 你能告诉我为什么在每次游标迭代之间备份日志吗?
  • @Josh 请参阅sqlperformance.com/2013/03/io-subsystem/chunk-deletes - 本质上,您可以用持续时间换取日志增长。备份日志通常需要更长的时间,但对日志文件本身的影响要小得多。而且您不必在每次迭代时都这样做,您可以根据批量大小和其他因素找到每 100 次迭代或每 1000 次迭代或您拥有的平衡点。任何给定系统上的最佳位置都可能不同。
【解决方案2】:

我知道这是一个旧线程,但我制作了 Arthur 光标解决方案的通用版本:

--Split a batch up into chunks using a cursor.
--This method can be used for most any large table with some modifications
--It could also be refined further with an @Day variable (for example)

DECLARE @Year INT
DECLARE @Month INT

DECLARE BatchingCursor CURSOR FOR
SELECT DISTINCT YEAR(<SomeDateField>),MONTH(<SomeDateField>)
FROM <Sometable>;


OPEN BatchingCursor;
FETCH NEXT FROM BatchingCursor INTO @Year, @Month;
WHILE @@FETCH_STATUS = 0
BEGIN

--All logic goes in here
--Any select statements from <Sometable> need to be suffixed with:
--WHERE Year(<SomeDateField>)=@Year AND Month(<SomeDateField>)=@Month   


  FETCH NEXT FROM BatchingCursor INTO @Year, @Month;
END;
CLOSE BatchingCursor;
DEALLOCATE BatchingCursor;
GO

这解决了我们的大表负载问题。

【讨论】:

    【解决方案3】:

    没有精灵尘埃,你知道的。

    如果不知道要传输的实际架构的细节,通用解决方案将与您描述的完全一样:将处理分成多个插入并跟踪键。这是一种伪代码 T-SQL:

    create table currentKeys (table sysname not null primary key, key sql_variant not null);
    go
    
    declare @keysInserted table (key sql_variant);
    declare @key sql_variant;
    begin transaction
    do while (1=1)
    begin
        select @key = key from currentKeys where table = '<target>';
        insert into <target> (...)
        output inserted.key into @keysInserted (key)
        select top (<batchsize>) ... from <source>
        where key > @key
        order by key;
    
        if (0 = @@rowcount)
           break; 
    
        update currentKeys 
        set key = (select max(key) from @keysInserted)
        where table = '<target>';
        commit;
        delete from @keysInserted;
        set @key = null;
        begin transaction;
    end
    commit
    

    如果您希望允许并行批处理并对键进行分区,则会变得更加复杂。

    【讨论】:

      【解决方案4】:

      这看起来像是一份好工作'BCP

      【讨论】:

        【解决方案5】:

        您可以使用 BCP 命令加载数据并使用 Batch Size 参数

        http://msdn.microsoft.com/en-us/library/ms162802.aspx

        两步法

        • 从视图到文本文件的 BCP OUT 数据
        • BCP IN 数据从文本文件到具有批量大小参数的表中

        【讨论】:

        • 13 秒! 握着小拳头
        • @Raj:将鼠标悬停在相对时间戳上以获取实际时间戳(秒)
        • 随着时间的推移,它会变为几分钟,然后是几小时,然后是几天。很漂亮,以前从没想过
        【解决方案6】:

        您可以对数据进行分区并将数据插入游标循环中。这与 SSIS 批量插入几乎相同。但在您的服务器上运行。

        create cursor ....
        select YEAR(DateCol), MONTH(DateCol) from whatever
        
        while ....
            insert into yourtable(...)
            select * from whatever 
            where YEAR(DateCol) = year and MONTH(DateCol) = month
        end
        

        【讨论】:

        • 这是我最终使用的策略。
        猜你喜欢
        • 2018-08-18
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多