bcp / BULK INSERT与表值参数的性能


83

BULK INSERT由于架构已更改,我将不得不使用SQL Server的命令重写一些相当旧的代码,我想到也许我应该考虑改用TVP切换到存储过程,但是我想知道会有什么效果它可能会影响性能。

一些背景信息可能有助于解释为什么我问这个问题:

  • 数据实际上是通过Web服务输入的。该Web服务将文本文件写入数据库服务器上的共享文件夹,该文件夹随后执行BULK INSERT。此过程最初是在SQL Server 2000上实现的,当时实际上除了INSERT在服务器上删除几百条语句外,别无选择,这实际上是原始过程,并且会造成性能灾难。

  • 数据被批量插入到永久登台表中,然后合并到更大的表中(此后将从登台表中删除)。

  • 要插入的数据量是“大”的,而不是“巨大的”-通常只有几百行,在极少数情况下可能是5-10k行。因此,我的直觉是,BULK INSERT作为一个未记录的操作不会太大的不同(但我当然不确定,因此是问题)。

  • 插入实际上是更大的流水线批处理过程的一部分,需要连续多次进行。因此性能至关重要的。

我想将其替换BULK INSERT为TVP的原因是:

  • 通过NetBIOS编写文本文件可能已经花费了一些时间,并且从体系结构的角度来看这是非常可怕的。

  • 我认为可以(并且应该)取消登台表。造成这种情况的主要原因是,插入的数据需要在插入的同时用于其他两次更新,而尝试从大量生产表进行更新要比使用几乎为空的登台要昂贵得多表。对于TVP,参数基本上临时表,我可以在主插入之前/之后使用它进行任何操作。

  • 我几乎可以消除重复检查,清理代码以及与批量插入相关的所有开销。

  • 如果服务器一次获得一些事务,则无需担心登台表或tempdb上的锁争用(我们尝试避免这种情况,但它确实发生了)。

显然,在将任何产品投入生产之前,我将对此进行概要分析,但是我认为在我度过所有时间之前先询问一下是一个好主意,看看是否有人为此发布使用TVP的严厉警告。

所以-对于任何对SQL Server 2008足够熟悉的人尝试过或至少对此进行过调查,那么结论是什么?对于插入(例如几百到几千行)的情况,TVP是否会切芥菜?与散装刀片相比,性能有显着差异吗?


更新:现在问号减少了92%!

(又称:测试结果)

经过36阶段的部署过程之后,最终结果现已投入生产。两种解决方案都经过了广泛的测试:

  • 提取共享文件夹代码并SqlBulkCopy直接使用该类;
  • 使用TVP切换到存储过程。

以便读者可以了解到底经过了什么测试,以消除对这些数据的可靠性的任何疑问,这是对该导入过程实际作用的更详细说明:

  1. 从通常约20至50个数据点的时间数据序列开始(尽管有时可能会增加几百个)。

  2. 对其进行大量疯狂的处理,这些处理基本上独立于数据库。此过程是并行的,因此(1)中大约8-10个序列被同时处理。每个并行过程会生成3个附加序列。

  3. 取所有3个序列和原始序列,并将它们合并为一批。

  4. 将现在完成的所有8-10个处理任务中的批次合并为一个大的超级批次。

  5. 使用BULK INSERT策略(请参阅下一步)或TVP策略(跳至步骤8)导入它。

  6. 使用SqlBulkCopy该类将整个超级批处理转储到4个永久登台表中。

  7. 运行一个存储过程,该存储过程是(a)在两个表上执行一系列聚合步骤,包括几个JOIN条件,然后(b)MERGE在6个生产表上使用聚合和非聚合数据执行a 。(完成)

    要么

  8. 生成4个DataTable对象,其中包含要合并的数据;其中3个包含CLR类型,但不幸的是ADO.NET TVP没有正确地支持它们,因此必须将它们作为字符串表示形式使用,这会严重影响性能。

  9. 将TVP馈送到存储过程,该过程基本上与(7)相同,但直接与接收到的表一起进行。(完成)

结果相当接近,但是TVP方法最终平均表现更好,即使数据超过1000行少量。

请注意,此导入过程连续运行了数千次,因此仅通过计算完成所有合并所花费的小时数(是,小时),就很容易获得平均时间。

最初,平均合并几乎要花8秒钟才能完成(在正常负载下)。消除NetBIOS的麻烦并切换为SqlBulkCopy将时间减少到几乎恰好7秒。切换到TVP可以将时间进一步减少到每批5.2秒。对于运行时间以小时为单位的过程,这将使吞吐量提高35% -一点也不差。与相比,也提高了约25%SqlBulkCopy

实际上,我相当有信心,真正的进步远不止于此。在测试过程中,很明显,最终的合并不再是关键的路径。相反,进行所有数据处理的Web服务开始受到输入请求数量的限制。CPU和数据库I / O都没有真正达到极限,并且没有明显的锁定活动。在某些情况下,我们发现连续合并之间存在几秒钟的空闲间隔。使用时有一点缝隙,但要小得多(半秒左右)SqlBulkCopy。但是我想那将成为另一天的故事。

结论:表值参数确实比BULK INSERT在中型数据集上执行复杂的导入+转换过程的操作要好。


我想补充一点,只是为了缓解对部分赞成登台的人的担忧。从某种意义上说,整个服务是一个巨大的升级过程。该过程的每一步都经过了严格的审核,因此我们不需要临时表来确定某些特定合并失败的原因(尽管在实践中几乎从未发生过)。我们要做的就是在服务中设置一个调试标志,它将中断调试器或将其数据转储到文件而不是数据库中。

换句话说,我们已经对流程有足够的洞察力,不需要临时表的安全性;我们首先拥有登台表的唯一原因是为了避免对所有本来必须使用的INSERTandUPDATE语句造成混乱。在原始过程中,临时数据无论如何仅停留在临时表中几分之一秒,因此它在维护/可维护性方面没有任何价值。

还要注意,我们还没BULK INSERT有用TVP代替每一个操作。一些处理大量数据和/或不需要对数据做任何特殊处理的操作(除了将其扔给数据库外)仍在使用SqlBulkCopy我并不是说TVP是性能的灵丹妙药,只是他们SqlBulkCopy在此特定情况下成功了,包括在初始阶段和最终合并之间进行了多次转换。

所以你有它。Point会去TToni寻找最相关的链接,但是我也很感谢其他回复。再次感谢!


这本身就是一个了不起的问题,我认为更新部分应该在答案中;)
Marc.2377'1

Answers:


10

我真的没有跟TVP经验,但,但有在MSDN一个很好的性能对比图与BULK INSERT这里

他们说,批量插入具有较高的启动成本,但此后速度更快。在远程客户端方案中,他们在大约1000行处绘制线(用于“简单”服务器逻辑)。从他们的描述来看,我会说您应该使用TVP。性能上的损失(如果有的话)可以忽略不计,并且体系结构上的好处似乎非常好。

编辑:在旁注中,您可以避免服务器本地文件,并通过使用SqlBulkCopy对象仍然使用批量复制。只需填充一个DataTable,并将其输入到SqlBulkCopy实例的“ WriteToServer” -Method中即可。易于使用,而且速度非常快。


感谢您提供的链接,这实际上非常有用,因为当数据提供复杂的逻辑时(它确实如此),MS似乎建议使用TVP,而且我们还能够调高或调低批处理的数量,因此我们不会超出范围。 1k行痛点。基于此,即使最终速度太慢,也值得花时间至少尝试一下。
亚罗诺(Aaronaught)2010年

是的,链接很有趣。@Aaronaught-在这种情况下,始终值得探索和分析潜在方法的性能,因此,我很想听听您的发现!
AdaTheDev 2010年

7

关于@TToni答案中提供的链接的图表需要在上下文中获取。我不确定对这些建议有多少实际研究(还请注意,该图表似乎仅在该文档的20082008 R2版本中可用)。

另一方面,SQL Server客户咨询团队提供了此白皮书:利用TVP最大化吞吐量

自2009年以来,我一直在使用TVP,并且至少根据我的经验,发现除了简单地将其插入到目标表中之外,没有其他逻辑需求(这种情况很少发生)之外,TVP通常是更好的选择。

我倾向于避免暂存表,因为数据验证应在应用程序层进行。通过使用易于容纳的TVP,并且存储过程中的TVP表变量就其本质而言,它是本地化的登台表(因此,与使用真实表进行登台时获得的同时运行的其他进程没有冲突) )。

关于在课题中进行的测试,我认为可以证明它比最初发现的速度更快:

  1. 除非在将值发送到TVP之外您的应用程序已经使用了DataTable,否则您不应使用DataTable。使用该IEnumerable<SqlDataRecord>接口速度更快,并且占用的内存更少,因为您不是在内存中复制集合,而只是将其发送到DB。我在以下地方对此进行了记录:
  2. TVP是表变量,因此不维护统计信息。这意味着,他们仅向查询优化器报告只有1行。因此,在您的过程中,要么:
    • 除了简单的SELECT以外,对使用TVP的任何查询使用语句级重新编译: OPTION (RECOMPILE)
    • 创建一个本地临时表(即single #)并将TVP的内容复制到临时表中

4

我认为我仍然会坚持使用批量插入方法。您可能会发现,使用合理数量的行的TVP仍然会击中tempdb。这是我的直觉,我不能说我已经测试了使用TVP的性能(不过我也很想听听其他人的意见)

您没有提到是否使用.NET,但是我用来优化先前解决方案的方法是使用SqlBulkCopy类进行大量数据加载-您无需先将数据写入文件中加载时,只需为SqlBulkCopy类(例如)提供一个DataTable-这是将数据插入DB的最快方法。5-10K的行并不多,我已经使用了多达750K的行。我怀疑通常来说,只有几百行,使用TVP并不会带来很大的不同。但是扩大恕我直言是有限的。

也许SQL 2008中的新MERGE功能会使您受益?

另外,如果您现有的登台表是用于此过程的每个实例的单个表,并且您担心争用等问题,那么是否考虑过每次创建一个新的“临时”但物理登台表,然后在创建时将其删除完成了吗?

请注意,您可以通过不带任何索引的填充来优化对该登台表的加载。然后,一旦填充,就在该点添加任何必需的索引(FILLFACTOR = 100,以获得最佳读取性能,因为此时它不会被更新)。


我确实使用.NET,并且该过程恰好是早先的,SqlBulkCopy而且从未更改过。感谢您提醒我有关此事,可能值得再次探讨。 MERGE临时表也已被广泛使用,以前曾尝试过临时表,但发现它更慢且更难管理。感谢您的输入!
Aaronaught

-2

登台表很好!真的,我不想做任何其他事情。为什么?因为数据导入可能会发生意外更改(并且通常以您无法预料的方式,例如,这些列仍被称为名字和姓氏,但是姓氏列中有名字数据的时间,例如,易于使用登台表研究问题,因此您可以准确地看到导入处理的列中包含哪些数据。我认为使用内存表时很难找到。我知道很多人像我一样从事谋生工作,所有人都建议使用登台桌。我怀疑这是有原因的。

与重新设计流程相比,将小的模式更改进一步固定为工作流程更容易,并且耗时更少。如果它正在运行,并且没人愿意花数小时来更改它,那么只需修复由于架构更改而需要修复的内容。通过更改整个流程,您引入的潜在新错误要比对经过测试的现有工作流程进行小的更改要多得多。

您将如何取消所有数据清理任务?您可能会以不同的方式执行它们,但是仍然需要完成它们。同样,以您描述的方式更改流程非常冒险。

就我个人而言,这听起来像是您只是因为使用较旧的技术而生气,而不是有机会玩新玩具。除了批量插入之外,您似乎没有任何实际要更改的依据,因此2000。


27
SQL 2008已经存在两年了,这个过程已经存在了很长时间,这是我什至第一次考虑更改它。最后的时髦评论真的必要吗?
亚罗诺(Aaronaught)2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.