【问题标题】:SQL Server 2008 big INSERT still slow after wrapped in transactionSQL Server 2008 big INSERT 包裹在事务中后仍然很慢
【发布时间】:2012-10-23 12:54:47
【问题描述】:

我一直在查看我的查询中的一个大 INSERT 问题:

INSERT table_name
SELECT .....;

该表没有索引,需要大约 2000 万行才能插入其中。我在我们的一台服务器上运行 SQL Server 2008 R2 中的查询。原版表演约40分钟。然后我在这里阅读帖子,告诉我将INSERT 包装在BEGIN TRANSACTION / COMMIT 中。我这样做了,花费的时间减少到 6 分钟。

但是,当我尝试在接下来的几次中运行事务包装查询时,时间又回到了 40 分钟,就像 TRANSACTION 效果消失了一样。我不知道在接下来的运行中发生了什么。任何的想法?

添加:

另一个post 说 TRANSACTION 旨在用于数据一致性而不是性能,建议每 5K 行批量插入。如何将单个 INSERT SELECT 语句拆分为批次?我很困惑。

更新:

确实,我发现性能提升不是来自 TRANSACTION,而是可能来自服务器端表缓存,因为我接下来运行了几次,性能大约是 5 分钟。

【问题讨论】:

  • 单独select 需要多少时间?
  • @Lostdreamer 根据 SQL Server 的执行计划估计,SELECT 仅占总数的 5%
  • 您是使用任何前端编码插入数据还是只是在查询编辑器中运行 sql 查询?
  • 你说你没有索引,但是你有主键吗?
  • 如另一篇文章所述,事务与性能无关。您注意到的速度增加与交易无关(如果有的话)。

标签: sql sql-server


【解决方案1】:

当您在事务中包装许多INSERT ... VALUES 语句时,可能会大幅加速,因为您不必在每次插入后将脏数据页写入磁盘。但是,当您将单个 INSERT ... SELECT 包装在显式事务中时,并没有加速,因为甚至在此之前就存在隐式事务并且机制并没有真正改变。很可能同时在您的环境中发生了其他变化。

性能逐渐下降可能是由于目标表的增长,或者数据库的增长。前者永远不会停止增长,后者可能会随着您的数据库不断增长而变得更加可变/不可预测,因此它可能不是下降,而是趋势。

如果您始终可以确保将数据插入到空表中,请考虑更加激进并每次都将其删除。使用SELECT INTO 而不是INSERT ... SELECT。这可能会也可能不会满足您的参照完整性需求。不同语法的优点是不同的日志记录策略。

如果在下一次插入之前无法删除表,但您可以确保在INSERT 操作期间不会被其他连接访问,则可以使用隔离级别或表提示来避免锁定;但是,实现类似目标的更安全的方法是 TABLOCK 提示。通过在开始时锁定整个表格,这种暗示走向了极端。其他所有人都被排除在外,并且没有时间花在行级锁定上。

插入按目标表的(聚集)主键排序的数据。您可以考虑在 INSERT 期间暂时禁用 其他 索引,但不要轻易这样做,因为这只是另一种严重损害任何并发流量(如果存在)的方式。

注意您的 mdf 文件大小。避免您看到它以小幅度自动增长的情况。

最后的手段:做一些硬件使用规划并对目标表进行分区。为此,您需要从“请快一点”的心态转变为“我需要达到这个速度”的心态。维护起来要复杂得多。

【讨论】:

    【解决方案2】:

    可能有很多事情会影响这一点。正在使用的日志记录类型,服务器上还发生了什么,能够获得表排他锁的操作,硬件(主要是磁盘 IO),表上已经存在哪些索引等。

    插入 2000 万条记录将生成大量日志记录。您要确保执行最少记录的操作。为此,请考虑 SELECT INTO(如果可能)。但是,如果您坚持使用 INSERT SELECT,请考虑使 SELECT INTO 成为最小记录操作的因素。见http://msdn.microsoft.com/en-us/library/dd425070%28v=sql.100%29.aspx

    【讨论】:

    • 感谢您的 MSDN 链接。 WITH (TABLOCK) 在我的情况下很有用。
    【解决方案3】:

    如果这是您需要执行的常规任务,为什么不能创建一个 SSIS 包并在必须执行此操作时运行它。

    【讨论】:

    • 这是一项日常任务,只需简单地连接 2 个大表并将结果放入目标表中,所有这些都在同一个数据库中。 SSIS 包将如何帮助提高性能?
    • SSIS 将允许您将其分成多个批次。
    • SSIS 也比常规的插入查询更快
    【解决方案4】:

    要快速插入批量数据,请使用批量复制。

    您可以使用 BCP 实用程序:

    或者,从 .Net 或者您可以使用 Sql Native Client:

    或者在数据库中进行批量复制:

    declare @todo table( primaryKeyFieldName primary key)
    insert @todo select primaryKeyFieldName from SourceTable
    
    declare @batch table(primaryKeyFieldName primary key)
    delete @batch
    while exists (select 1 from @todo)
    begin 
        insert @batch select top 500 primaryKeyFieldName from @todo
        delete todo from @todo todo inner join @batch b on b.primaryKeyFieldName = todo.primaryKeyFieldName 
        insert DestinationTable(fields....)
        select s.fields, ....
        from SourceTable s inner join @batch b on s.primaryKeyFieldName = b.primaryKeyFieldName
    end
    

    【讨论】:

    • 我没有将数据复制或备份到文件或其他来源,我必须运行 SELECT SQL 查询以从某些表中获取选定的数据,并将它们投影到一个大表中。这些建议听起来与我的问题无关,但谢谢。
    【解决方案5】:

    我刚刚用“WITH(TABLOCK)”提示进行了测试,最终得到了令人满意的性能。与原来的 40 分钟相比,整个查询运行 3 分钟。这是一个巨大的改进,因为查询正在执行初始表填充工作,所以不用担心访问冲突。

    感谢大家的有益评论。

    【讨论】:

      猜你喜欢
      • 2013-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-11
      • 1970-01-01
      • 2018-01-20
      • 1970-01-01
      相关资源
      最近更新 更多