【发布时间】: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 应用程序?
标签: sql-server scalability bulkinsert sqlbulkcopy database-performance