如何在SQL Server 中模拟"Bulk" Insert with DataTable

How to mimic "Bulk" Insert with DataTable in SQL Server

我有一个存储过程,我在其中发送一个用户定义的类型 table。我进行了简化以使其更易于阅读。

 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 做这件事,如果一个失败,存储过程将继续尝试更新其他过程。

我能想到的唯一选择是一次只做一个(要么在多次调用存储过程的代码中循环,要么在存储过程中循环),但想知道性能如何hit 就是为了这个,或者如果有更好的解决方案。

不确定您想继续处理哪些错误,但除非您运行遇到各种意外错误,否则我会尽量避免陷入 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

如果可能的话,我会尝试坚持使用批量更新,并使用连接谓词来消除您期望看到的大多数错误的可能性。因为担心 "might" 系统关闭失败而更改为 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 的插入过程实际插入到一个轻量级的、最小的 keyed/constrainted table(如队列)中。然后,每隔一分钟左右,通过记录的调用有一个 Agent 作业 运行,并对其进行批量操作。不会从根本上改变这里的任何模式,但它使处理异步,因此调用者不必等待,并且通过将请求批处理在一起,您可以节省处理能力,依靠 SQL 最擅长的东西;基于集合的操作。

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

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

  • 只需正确构建 insert/update 语句即可避免大多数错误。

  • 如果需要,您可以使用 try/catch 和 "empty" catch 块,这样失败就不会停止整个处理过程

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