需要有关在 SQL Server 上使用 SqlBulkCopy 挑战极限的建议
Posted
技术标签:
【中文标题】需要有关在 SQL Server 上使用 SqlBulkCopy 挑战极限的建议【英文标题】:Need recommendations on pushing the envelope with SqlBulkCopy on SQL Server 【发布时间】:2012-06-25 07:40:03 【问题描述】:我正在设计一个应用程序,其中一个方面是它应该能够将大量数据接收到 SQL 数据库中。我将数据库结构设计为具有 bigint 标识的单个表,如下所示:
CREATE TABLE MainTable
(
_id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
field1, field2, ...
)
我将省略我打算如何执行查询,因为它与我的问题无关。
我已经编写了一个原型,它使用 SqlBulkCopy 将数据插入到这个表中。它似乎在实验室里工作得很好。我能够以约 3K 记录/秒的速度插入数千万条记录(完整记录本身相当大,约 4K)。由于该表上唯一的索引是自动递增 bigint,因此即使在推送了大量行之后,我也没有看到速度变慢。
考虑到实验室 SQL Server 是一个配置相对较弱的虚拟机(4Gb RAM,与其他 VM 磁盘 sybsystem 共享),我期望在物理机上获得显着更好的吞吐量,但它没有发生,或者可以说性能提升可以忽略不计。我可以,也许可以在物理机器上更快地插入 25%。即使我配置了 3 驱动器 RAID0,它的性能比单个驱动器快 3 倍(由基准测试软件测量),我也没有任何改进。基本上:更快的驱动子系统、专用物理 CPU 和双 RAM 几乎没有转化为任何性能提升。
然后我使用 Azure 上最大的实例(8 核,16Gb)重复了测试,得到了相同的结果。因此,添加更多内核并不会改变插入速度。
此时我已经使用了以下软件参数,但没有任何显着的性能提升:
修改 SqlBulkInsert.BatchSize 参数 同时从多个线程插入,并调整线程数 在 SqlBulkInsert 上使用表锁定选项 通过使用共享内存驱动程序从本地进程插入来消除网络延迟我试图将性能提高至少 2-3 倍,我最初的想法是投入更多的硬件就能完成任务,但到目前为止还没有。
那么,有人可以推荐我吗:
什么资源可能被怀疑是这里的瓶颈?如何确认? 考虑到只有一个 SQL 服务器系统,我是否可以尝试获得可靠的可扩展批量插入改进的方法?更新我确信加载应用程序不是问题。它在单独的线程中的临时队列中创建记录,因此当有插入时,它会像这样(简化):
===>start logging time
int batchCount = (queue.Count - 1) / targetBatchSize + 1;
Enumerable.Range(0, batchCount).AsParallel().
WithDegreeOfParallelism(MAX_DEGREE_OF_PARALLELISM).ForAll(i =>
var batch = queue.Skip(i * targetBatchSize).Take(targetBatchSize);
var data = MYRECORDTYPE.MakeDataTable(batch);
var bcp = GetBulkCopy();
bcp.WriteToServer(data);
);
====> end loging time
记录时间,创建队列的部分永远不会占用任何重要的块
UPDATE2我已经实现了收集该周期中每个操作需要多长时间,布局如下:
queue.Skip().Take()
- 可以忽略不计
MakeDataTable(batch)
- 10%
GetBulkCopy()
- 可以忽略不计
WriteToServer(data)
- 90%
UPDATE3 我正在为标准版本的 SQL 设计,所以我不能依赖分区,因为它只在企业版中可用。但我尝试了一种分区方案:
创建了 16 个文件组(G0 到 G15), 制作了 16 个仅用于插入的表(T0 到 T15),每个表都绑定到其单独的组。表根本没有索引,甚至没有聚集 int 标识。 插入数据的线程将循环遍历所有 16 个表。这几乎可以保证每个批量插入操作都使用自己的表这确实在批量插入方面产生了约 20% 的改进。 CPU 内核、LAN 接口、驱动器 I/O 未最大化,并以最大容量的 25% 左右使用。
UPDATE4 我认为它现在已经达到了最好的水平。我能够使用以下技术将插入物推到合理的速度:
每个批量插入都进入自己的表,然后将结果合并到主表中 每次批量插入都会重新创建表,并使用表锁 使用 IDataReader 实现 from here 而不是 DataTable。 从多个客户端完成的批量插入 每个客户端都使用单独的千兆 VLAN 访问 SQL 访问主表的副进程使用 NOLOCK 选项 我检查了 sys.dm_os_wait_stats 和 sys.dm_os_latch_stats 以消除争用我现在很难决定谁会因回答的问题而获得奖励。那些没有得到“答复”的人,我很抱歉,这是一个非常艰难的决定,我感谢大家。
UPDATE5:以下项目可以使用一些优化:
使用 IDataReader 实现 from here 而不是 DataTable。除非您在具有大量 CPU 核心数的机器上运行您的程序,否则它可能会使用一些重构。由于它使用反射来生成 get/set 方法,这成为 CPU 的主要负载。如果性能是关键,那么当您手动编写 IDataReader 代码时,它会增加很多性能,以便对其进行编译,而不是使用反射
【问题讨论】:
也许性能瓶颈是应用程序向 SqlBulkCopy 提供记录的能力? 在此期间您看到了什么样的资源利用率?如果没有此类信息,您将无法真正进行有效的性能调整。 虽然我们正在这样做,但如果 C# 应用程序正在生成行,多线程插入可能无济于事。在某些时候,GC 将运行并回收内存,并且(可能)挂起线程来执行此操作(取决于您正在运行的 .NET Framework 的版本,以及它是否是服务器/工作站 GC)。 @ta.speot.is & all:我有充分的理由相信用于插入虚拟行的应用程序没有问题(请参阅更新)。 @galets 是Skip
操作O(1)
还是O(n)
? MakeDataTable
听起来很贵。 WriteToServer
可以采用IDataReader
,您可以在发送到服务器的任何内容上实现一个轻量级的IDataReader
包装器。您是否分析过 .NET 应用程序?
【参考方案1】:
有关针对批量加载调整 SQL Server 的建议,请参阅 MS 的 Data Loading and Performance Guide 论文,以及在线书籍中的 Guidelines for Optimising Bulk Import。尽管他们专注于从 SQL Server 批量加载,但大多数建议适用于使用客户端 API 进行批量加载。本文适用于 SQL 2008 - 您没有说明您的目标是哪个 SQL Server 版本 两者都有相当多的信息,值得详细阅读。然而,一些亮点:
最少记录批量操作。使用批量记录或简单恢复。 您可能需要启用 traceflag 610(但请参阅注意事项) 这个) 调整批量大小 考虑对目标表进行分区 考虑在批量加载期间删除索引在Data Loading and Performance Guide 的流程图中得到了很好的总结:
正如其他人所说,您需要获得一些性能计数器来确定瓶颈的来源,因为您的实验表明 IO 可能不是限制。 Data Loading and Performance Guide 包含要监视的 SQL 等待类型和性能计数器列表(文档中没有要链接的锚点,但在“优化批量加载”部分中,这大约是整个文档的 75%)
更新
我花了一段时间才找到链接,但 Thomas Kejser 的 this SQLBits talk 也非常值得一看 - 如果您没有时间观看整件事,可以使用 slides。它重复了此处链接的一些材料,但还涵盖了有关如何处理特定性能计数器的高发生率的其他一些建议。
【讨论】:
我会查看您发布的链接,并感谢您的努力。但我确实必须注意,我确实将 db 置于简单恢复模式(没有日志),调整了批量大小(如我所述),关于索引和分区:只有一个索引,如果你看一下,它不应该影响插入速度。由于插入速度不会随着记录数而降低,我认为不需要分区 @galets - 添加了另一个链接。您的自动增量 PK 可能会限制速度,因为所有插入线程可能都在争夺表末尾的热页。如果是这种情况,请考虑上面链接的 Kejser SQLBits 演讲中描述的“哈希分区”。此外,进行一些测试以确保您获得最少的日志记录。简单恢复模式不会取消记录;事务日志将在事务期间写入(并增长)。 我测试了 _id 可能是一个问题的理论,方法是完全删除它并完全离开没有索引的表。好像没问题,速度还是和之前一样。不过,我非常感谢您的努力以及您提交的链接。他们给了我一些我会尝试的想法【参考方案2】:看来您已经做了很多,但我不确定您是否有机会研究 Alberto Ferrari SqlBulkCopy Performance Analysis 报告,该报告描述了考虑与 SqlBulkCopy 相关的性能的几个因素。我想说那篇论文中讨论的很多事情仍然值得尝试,最好先尝试。
【讨论】:
谢谢,很好的阅读,我从中得到了一些好主意。我无法完全实现的一件事是 100% 的 CPU、磁盘或网络利用率,这让我抓狂【参考方案3】:我不确定为什么 CPU、IO 或内存的利用率没有达到 100%。但是,如果您只是想提高批量加载速度,则需要考虑以下几点:
-
将您的数据文件分割成不同的文件。或者,如果它们来自不同的来源,则只需创建不同的数据文件。
然后同时运行多个批量插入。
根据您的情况,上述方法可能不可行;但如果可以的话,我相信它应该会提高你的加载速度。
【讨论】:
感谢您的建议,但我已经尝试过了。虽然它确实有效,但差异并不显着。请参阅我的 UPDATE3 评论。以上是关于需要有关在 SQL Server 上使用 SqlBulkCopy 挑战极限的建议的主要内容,如果未能解决你的问题,请参考以下文章