【问题标题】:How to mimic "Bulk" Insert with DataTable in SQL Server如何在 SQL Server 中使用 DataTable 模拟“批量”插入
【发布时间】:2018-09-26 02:49:53
【问题描述】:

我有一个存储过程,我在其中发送一个用户定义的类型,它是一个表。我已经简化以使其更易于阅读。

 CREATE TYPE [dbo].[ProjectTableType] AS TABLE(
     [DbId] [uniqueidentifier] NOT NULL,
     [DbParentId] [uniqueidentifier] NULL,
     [Description] [text] NULL
 )

 CREATE PROCEDURE [dbo].[udsp_ProjectDUI] (@cmd varchar(10), 
      @tblProjects ProjectTableType READONLY) AS BEGIN
      DECLARE @myNewPKTable TABLE (myNewPK uniqueidentifier)

      IF(LOWER(@cmd) = 'insert')
      BEGIN
          INSERT INTO 
            dbo.Project 
            (
                DbId,
                DbParentId,
                Description)
        OUTPUT INSERTED.DbId INTO @myNewPKTable
        SELECT NEWID(),
                DbParentId,
                Description
        FROM @tblProjects;

        SELECT * FROM dbo.Project WHERE dbid IN (SELECT myNewPK FROM @myNewPKTable);
  END

这是用于其他应用程序将使用的 DLL,因此我们不一定负责验证。我想模仿 BULK INSERT,如果其中一行无法插入,但其他行很好,则仍然会插入正确的行。有没有办法做到这一点?我也想为 UPDATE 做这件事,如果一个失败,存储过程将继续尝试更新其他的。

我能想到的唯一选择是一次只做一个(要么是代码中的循环,其中存储过程被多次调用,要么是存储过程中的循环),但想知道性能会受到什么影响或者如果有更好的解决方案。

【问题讨论】:

    标签: sql-server tsql bulkinsert


    【解决方案1】:

    不确定您要继续处理哪些错误,但除非您遇到各种意外错误,否则我会尽量避免转入 RBAR。

    检查明确的违规行为

    我认为主要的事情是 PK 变化,您可以通过在插入(和更新)之前检查是否存在来避免这种变化。如果还有其他业务逻辑失败的情况,也可以在这里查看。

    insert into dbo.Project 
    (
        DbId,
        DbParentId,
        Description
    )
    output insert.DbId 
    into @myNewPKTable (DbId)
    select
        DbId = newid(),
        DbParentId = s.DbParentId,
        Description = s.Description
    from @tblProjects s -- source
    -- LOJ null check makes sure we don't violate PK
    -- NOTE: I'm pretending this is an alternate key of the table. 
    left outer join dbo.Project t -- target
        on s.dbParentId = t.dbParentId 
    where t.dbParentId is null
    

    如果可能的话,我会尝试坚持批量更新,并使用连接谓词来消除您希望看到的大多数错误的可能性。因为担心“可能”导致系统关闭失败而更改为 RBAR 处理可能是浪费时间。然后,如果你遇到了一个非常严重的错误,你无法从中恢复,那么合法地使批处理失败。

    RBAR

    或者,如果您绝对需要成功或失败的逐行粒度,您可以在每个语句周围执行 try/catch,并让 catch 块不执行任何操作(或记录某些内容)。

    declare 
        @DBParentId int, 
        @Description nvarchar(1000),
        @Ident int
    
    declare c cursor local fast_forward for
        select
            DbParentId = s.DbParentId,
            Description = s.Description
        from @tblProjects
    open c
    
    fetch next from c into @DBParentId, @Description
    
    while @@fetch_status = 0
    begin
    
        begin try
    
            insert into dbo.Project 
            (
                DbId,
                DbParentId,
                Description
            )
            output insert.DbId 
            into @myNewPKTable (DbId)
            select
                newid(),
                @DBParentId,
                @Description
    
        end try
        begin catch
            -- log something if you want
            print error_message()
        end catch
    
        fetch next from c into @DBParentId, @Description
    
    end
    

    混合 您可能会变得聪明并混合事物。一种选择可能是使面向 Web 的插入过程实际插入到轻量级、最小键控/约束的表(如队列)中。然后,每隔一分钟左右,让一个代理作业通过记录的呼叫运行,并批量操作它们。没有从根本上改变这里的任何模式,但它使处理异步,因此调用者不必等待,并且通过将请求批处理在一起,您可以节省处理能力,搭载 SQL 最擅长的;基于集合的操作。

    另一种选择可能是尽可能地进行基于集合的处理(使用检查来防止违反业务规则或约束)。如果有任何失败,您可以为剩余的行拆分一个 RBAR 进程。如果一切都成功,则永远不会命中该 RBAR 进程。

    结论 有几种方法可以解决这个问题。除非您有非常充分的理由需要逐行粒度,否则我会尝试尽可能多地使用基于集合的操作。

    • 您可以通过正确构造insert/update 语句来避免大多数错误。

    • 如果需要,您可以使用带有“空”catch 块的try/catch,这样故障不会停止整个处理

    • 根据具体情况的随机可能性,您可能希望或需要混合使用这两种方法

    【讨论】:

      猜你喜欢
      • 2016-12-25
      • 2011-06-17
      • 2010-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多