我将不得不使用 SQL Server 的BULK INSERT
命令重写一些相当旧的代码,因为架构已经改变,我想到也许我应该考虑改用 TVP 切换到存储过程,但我想知道什么效果它可能对性能有影响。
一些背景信息可能有助于解释我为什么要问这个问题:
数据实际上是通过 Web 服务传入的。Web 服务将一个文本文件写入数据库服务器上的共享文件夹,该文件夹依次执行
BULK INSERT
. 这个过程最初是在 SQL Server 2000 上实现的,当时除了在服务器上扔几百条语句之外真的别无选择INSERT
,这实际上是原始过程,并且是性能灾难。数据被批量插入到永久暂存表中,然后合并到一个更大的表中(之后从暂存表中删除)。
要插入的数据量是“大”的,但不是“巨大的”——通常是几百行,在极少数情况下可能会超过 5-10k 行。因此,我的直觉是,
BULK INSERT
作为一个非日志操作不会有那么大的区别(但我当然不确定,因此是这个问题)。插入实际上是一个更大的流水线批处理过程的一部分,需要连续发生多次;因此性能至关重要。
我想BULK INSERT
用 TVP 替换的原因是:
在 NetBIOS 上编写文本文件可能已经花费了一些时间,而且从体系结构的角度来看是相当可怕的。
我相信临时表可以(并且应该)被淘汰。它存在的主要原因是插入的数据需要在插入的同时用于其他几个更新,并且尝试从大量生产表中进行更新比使用几乎为空的 staging 成本要高得多桌子。使用 TVP,参数基本上是临时表,我可以在主插入之前/之后用它做任何我想做的事情。
我几乎可以消除重复检查、清理代码以及与批量插入相关的所有开销。
如果服务器一次获得一些此类事务,则无需担心临时表或 tempdb 上的锁争用(我们试图避免它,但它确实发生了)。
我显然会在将任何东西投入生产之前对此进行分析,但我认为在我花费所有时间之前先询问一下可能是个好主意,看看是否有人有任何关于为此目的使用 TVP 的严厉警告。
所以 - 对于任何对 SQL Server 2008 足够熟悉并尝试过或至少对此进行过调查的人来说,结论是什么?比如说,几百到几千行的插入,在相当频繁的基础上发生,TVP 会减少芥末吗?与批量插入相比,性能是否存在显着差异?
更新:现在问号减少了 92%!
(又名:测试结果)
经过 36 个阶段的部署过程后,最终结果现已投入生产。两种解决方案都经过了广泛的测试:
- 删除共享文件夹代码并
SqlBulkCopy
直接使用该类; - 使用 TVP 切换到存储过程。
为了让读者可以了解究竟测试了什么,以消除对这些数据可靠性的怀疑,这里是这个导入过程实际作用的更详细的解释:
从通常有 20-50 个数据点的时间数据序列开始(尽管有时可能多达几百个);
对它进行一大堆疯狂的处理,这些处理大多独立于数据库。这个过程是并行的,因此(1)中大约有 8-10 个序列同时被处理。每个并行进程生成 3 个附加序列。
取所有 3 个序列和原始序列并将它们组合成一个批次。
将所有 8-10 个现已完成的处理任务中的批次组合成一个大的超级批次。
使用
BULK INSERT
策略(参见下一步)或 TVP 策略(跳至步骤 8)导入它。使用
SqlBulkCopy
该类将整个超级批次转储到 4 个永久暂存表中。运行一个存储过程,它 (a) 对 2 个表执行一系列聚合步骤,包括几个
JOIN
条件,然后 (b)MERGE
使用聚合数据和非聚合数据对 6 个生产表执行 a。(完成的)或者
生成4个
DataTable
包含待合并数据的对象;其中 3 个包含 CLR 类型,遗憾的是 ADO.NET TVP 没有正确支持这些类型,因此必须将它们作为字符串表示形式插入,这会稍微损害性能。将 TVP 馈送到存储过程,该过程与 (7) 进行基本相同的处理,但直接使用接收到的表。(完成的)
结果相当接近,但 TVP 方法最终平均表现更好,即使数据稍微超过 1000 行也是如此。
请注意,此导入过程连续运行数千次,因此只需计算完成所有合并所需的时间(是的,小时),就很容易获得平均时间。
最初,平均合并几乎需要 8 秒才能完成(在正常负载下)。删除 NetBIOS 杂物并切换到SqlBulkCopy
将时间减少到几乎正好 7 秒。切换到 TVP 进一步将时间减少到每批5.2 秒。对于一个运行时间以小时为单位的进程来说,这提高了35%的吞吐量——所以一点也不差。它也比SqlBulkCopy
.
我实际上相当有信心,真正的改进远不止于此。在测试过程中,很明显最终合并不再是关键路径。相反,执行所有数据处理的 Web 服务开始因请求的数量而崩溃。CPU 和数据库 I/O 都没有真正达到极限,也没有明显的锁定活动。在某些情况下,我们看到连续合并之间有几秒钟的空闲时间。使用时有一点差距,但要小得多(半秒左右)SqlBulkCopy
。但我想这将成为另一天的故事。
结论:表值参数确实比BULK INSERT
在中型数据集上运行的复杂导入+转换过程执行得更好。
我想补充一点,只是为了减轻部分支持登台的人的疑虑。在某种程度上,这整个服务是一个巨大的分期过程。该过程的每一步都经过严格审核,因此我们不需要临时表来确定某些特定合并失败的原因(尽管实际上它几乎从未发生过)。我们所要做的就是在服务中设置一个调试标志,它将中断调试器或将其数据转储到文件而不是数据库中。
换句话说,我们已经对流程有足够的了解,不需要临时表的安全性;我们首先拥有临时表的唯一原因是避免在所有我们必须使用的INSERT
和语句上颠簸。UPDATE
在最初的过程中,暂存数据在暂存表中只存在几分之一秒,因此在维护/可维护性方面没有增加任何价值。
另请注意,我们并没有用 TVP替换每个BULK INSERT
操作。处理大量数据和/或不需要对数据做任何特殊处理(除了将其扔到数据库之外)的几个操作仍然使用SqlBulkCopy
. 我并不是说 TVP 是一种性能灵丹妙药,只是说它们SqlBulkCopy
在这个特定的实例中成功了,涉及初始阶段和最终合并之间的几次转换。
所以你有它。指向 TToni 寻找最相关的链接,但我也很欣赏其他回复。再次感谢!