4

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

INSERT table_name
SELECT .....;

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

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

添加:

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

更新:

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

4

5 回答 5

1

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

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

于 2012-10-23T14:25:48.360 回答
1

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

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

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

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

插入按目标表的(聚集)主键排序的数据。您可以考虑在您INSERT.

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

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

于 2012-10-23T13:58:52.223 回答
0

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

您可以使用 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
于 2012-10-23T13:06:09.290 回答
0

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

于 2012-10-23T13:08:04.947 回答
0

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

谢谢大家的有益评论。

于 2012-10-23T15:25:19.923 回答