【问题标题】:SQL Server insert performanceSQL Server 插入性能
【发布时间】:2012-04-28 01:42:57
【问题描述】:

我有一个像这样生成的插入查询

INSERT INTO InvoiceDetail (LegacyId,InvoiceId,DetailTypeId,Fee,FeeTax,Investigatorid,SalespersonId,CreateDate,CreatedById,IsChargeBack,Expense,RepoAgentId,PayeeName,ExpensePaymentId,AdjustDetailId) 
VALUES(1,1,2,1500.0000,0.0000,163,1002,'11/30/2001 12:00:00 AM',1116,0,550.0000,850,NULL,@ExpensePay1,NULL); 
DECLARE @InvDetail1 INT; SET @InvDetail1 = (SELECT @@IDENTITY);

此查询仅针对 110K 行生成。

执行所有这些查询需要 30 分钟

我检查了查询计划,最大的 % 节点是

以 57% 的查询成本插入聚集索引 其中有一个我不想发布的长 xml。

查询成本为 38% 的 Table Spool

<RelOp AvgRowSize="35" EstimateCPU="5.01038E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="1" LogicalOp="Eager Spool" NodeId="80" Parallel="false" PhysicalOp="Table Spool" EstimatedTotalSubtreeCost="0.0466109">
  <OutputList>
    <ColumnReference Database="[SkipPro]" Schema="[dbo]" Table="[InvoiceDetail]" Column="InvoiceId" />
    <ColumnReference Database="[SkipPro]" Schema="[dbo]" Table="[InvoiceDetail]" Column="InvestigatorId" />
    <ColumnReference Column="Expr1054" />
    <ColumnReference Column="Expr1055" />
  </OutputList>
  <Spool PrimaryNodeId="3" />
</RelOp>

所以我的问题是我可以做些什么来提高这件事的速度?我已经跑了 ALTER TABLE TABLENAME NOCHECK 约束所有 在查询之前然后 ALTER TABLE TABLENAME NOCHECK 约束所有 在查询之后。

这并没有减少任何时间。

知道我正在使用 SqlCommand 对象发送查询的 .NET 应用程序中运行这些查询。

然后我尝试将 sql 命令输出到一个文件,然后使用 sqlcmd 执行它,但我没有得到任何关于它如何做的更新,所以我放弃了。

有什么想法、提示或帮助吗?

更新:

好的,你们所有人都非常有帮助。在这种情况下,我希望我可以将多个答案归功于多个答案。

解决此问题的方法有两个。

第一个:

1) 我禁用/重新启用了所有外键(比删除它们容易得多)

ALTER TABLE TableName NOCHECK CONSTRAINT ALL
ALTER TABLE TableName CHECK CONSTRAINT ALL

2) 我禁用/重新启用了索引(同样比删除容易得多)

ALTER INDEX [IX_InvoiceDetail_1] ON [dbo].[InvoiceDetail] DISABLE
ALTER INDEX [IX_InvoiceDetail_1] ON [dbo].[InvoiceDetail] REBUILD PARTITION = ALL WITH ( PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, ONLINE = OFF, SORT_IN_TEMPDB = OFF )

第二个:

我将所有插入语句包装到一个事务中。我最初不知道如何在 .NET 中做到这一点。

我非常感谢我收到的所有意见。

如果我曾经进行这种从 DB 到 DB 的翻译,我肯定会从 BULK INSERT 开始。它似乎更加灵活和快速。

【问题讨论】:

  • 告诉我们更多关于您的聚集索引和插入的信息。您的插入是否与您的聚集索引相关,以便所有记录都将附加在现有记录之后? (即,记录与聚集索引自然顺序排列)
  • 永远不要使用@@Identity!你可以用这个来搞乱数据完整性,因为它并不总是返回正确的值。请改用 scope_Identity()。
  • 我知道这已经过时了,但您还记得最终查询花费了多少时间吗?只是想知道这一切有多大不同?

标签: sql-server insert bulkinsert


【解决方案1】:

听起来插入导致 SQL Server 重新计算索引。一种可能的 解决方案是删除索引,执行插入,然后重新添加索引。使用您尝试的解决方案,即使您告诉它忽略约束,它仍然需要保持索引更新。

【讨论】:

    【解决方案2】:

    这很可能是提交刷新等待。如果您不将 INSERT 集包装到显式管理的事务中,则每个 INSERT 都是其自己的自动提交事务。这意味着每个 INSERT 都会自动发出一个提交,并且提交必须等到日志是持久的(即写入磁盘)。每次插入后刷新日志非常慢。

    例如,尝试在单行提交样式中插入 100k 行:

    set nocount on; 
    declare @start datetime = getutcdate();  
    
    declare @i int = 0;
    while @i < 100000
    begin
    INSERT INTO InvoiceDetail (
      LegacyId,InvoiceId,DetailTypeId,Fee,
      FeeTax,Investigatorid,SalespersonId,
      CreateDate,CreatedById,IsChargeBack,
      Expense,RepoAgentId,PayeeName,ExpensePaymentId,
      AdjustDetailId) 
      VALUES(1,1,2,1500.0000,0.0000,163,1002,
        '11/30/2001 12:00:00 AM',
        1116,0,550.0000,850,NULL,1,NULL); 
      set @i = @i+1;
    end
    
    select datediff(ms, @start, getutcdate());
    

    这在我的服务器上运行大约 12 秒。但是添加事务管理并每 1000 行提交一次,插入 100k 行只持续大约 4 秒:

    set nocount on;  
    declare @start datetime = getutcdate();  
    
    declare @i int = 0;
    begin transaction
    while @i < 100000
    begin
    INSERT INTO InvoiceDetail (
      LegacyId,InvoiceId,DetailTypeId,
      Fee,FeeTax,Investigatorid,
      SalespersonId,CreateDate,CreatedById,
      IsChargeBack,Expense,RepoAgentId,
      PayeeName,ExpensePaymentId,AdjustDetailId) 
      VALUES(1,1,2,1500.0000,0.0000,163,1002,
        '11/30/2001 12:00:00 AM',
        1116,0,550.0000,850,NULL,1,NULL); 
      set @i = @i+1;
      if (@i%1000 = 0)
      begin
        commit
        begin transaction
      end  
    end
    commit;
    select datediff(ms, @start, getutcdate());
    

    另外考虑到即使没有批量提交,我也可以在 12 秒内插入 100k 行,而您需要 30 分钟,它值得研究 1) IO 子系统的速度(例如,您在驱动器)和 2)客户端代码在从一个调用中检索 @@identity 和调用下一个插入之间还做了什么。可能大部分时间都在堆栈的客户端。一种简单的解决方案是并行启动多个插入 (BeginExecuteNonQuery),以便您不断提供 SQL Server 插入。

    【讨论】:

    • 您的负载完全在数据库服务器上运行。我认为他一次通过网络发送他的插入,这就是为什么它们很慢。可能更多是由于网络延迟和往返而不是实际的提交刷新开销。
    • 我刚刚运行了我认为合理地复制他的 110K 插入语句的方法,一个一个地,通过网络,是的,首先启动一个事务将吞吐量提高了一个数量级!
    • @RalphShillington 使用 SQL Server 2014 及更高版本,您还可以选择 lazy durability
    【解决方案3】:

    您是否一次从 .Net 客户端执行这些查询(即向 SQL Server 发送 110,000 个单独的查询请求)?

    在这种情况下,很可能是网络延迟和将这些 INSERT 发送到 SQL Server 而不进行批处理的其他开销,而不是 SQL Server 本身。

    查看大容量插入。

    【讨论】:

      【解决方案4】:

      您已将此问题标记为“bulkinsert”。那么为什么不使用BULK INSERT 命令呢?

      如果您想要更新进度,您可以将批量插入拆分成更小的部分,并在每个部分完成后更新进度。

      【讨论】:

      【解决方案5】:

      你可以做几件事:

      1) Disable any triggers on this table
      2) Drop all indexes
      3) Drop all foreign keys
      4) Disable any check constraints
      

      【讨论】:

        【解决方案6】:

        运行单个 INSERT 始终是最慢的选项。另外 - @@IDENTITY 是怎么回事 - 看起来你不需要跟踪介于两者之间的那些。

        如果您不想从文件或 SSIS 中使用 BULK INSERT,那么如果您绝对必须在 .NET 程序中执行此操作,那么 SqlBulkCopy feature in ADO.NET 可能是您最好的选择。

        110k 行的导入时间应该比我研究和写这个答案的时间要少。

        【讨论】:

          【解决方案7】:

          一些提高插入性能的建议:

          • 增加 ADO.NET BatchSize
          • 明智地选择目标表的聚集索引,这样插入不会导致聚集索引节点分裂(例如 autoinc 列)
          • 先插入临时堆表,然后发出一个大的“insert-by-select”语句将所有暂存表数据推送到实际目标表中
          • 应用 SqlBulkCopy
          • 在插入之前放置一个表锁(如果您的业务场景允许的话)

          取自Tips For Lightning-Fast Insert Performance On SqlServer

          【讨论】:

            【解决方案8】:

            嗯,让它运行,检查性能计数器。你看到了什么?你有什么光盘布局?我可以在 30 分钟内插入几百万行 - 准确地说,将近一亿行(实时财务信息,到其他 3 个表的链接)。我敢打赌你的 IO 布局很糟糕(即磁盘结构错误,文件分配错误)

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-03-31
              • 1970-01-01
              • 1970-01-01
              • 2011-11-16
              • 2019-03-04
              • 2011-02-17
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多