4

我正在设计一个应用程序,其中一个方面是它应该能够将大量数据接收到 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 实现而不是 DataTable。
  • 从多个客户端完成的批量插入
  • 每个客户端都使用单独的千兆 VLAN 访问 SQL
  • 访问主表的副进程使用 NOLOCK 选项
  • 我检查了 sys.dm_os_wait_stats 和 sys.dm_os_latch_stats 以消除争用

在这一点上,我很难决定谁回答了问题。那些没有得到“答复”的人,我很抱歉,这是一个非常艰难的决定,我感谢大家。

UPDATE5:以下项目可以使用一些优化:

  • 从这里使用 IDataReader 实现而不是 DataTable。

除非您在具有大量 CPU 核心数的机器上运行您的程序,否则它可能会使用一些重构。由于它使用反射来生成 get/set 方法,这成为 CPU 的主要负载。如果性能是关键,当您手动编写 IDataReader 代码时,它会增加很多性能,以便编译它,而不是使用反射

4

3 回答 3

4

有关为批量加载调整 SQL Server 的建议,请参阅 MS 的数据加载和性能指南论文,以及从在线书籍优化批量导入的指南。尽管他们专注于从 SQL Server 批量加载,但大多数建议适用于使用客户端 API 进行批量加载。这篇论文适用于 SQL 2008 - 你没有说你的目标是哪个 SQL Server 版本
两者都有相当多的信息,值得详细阅读。但是,一些亮点:

  • 最少记录批量操作。使用批量记录或简单恢复。您可能需要启用 traceflag 610(但请参阅执行此操作的注意事项)
  • 调整批量大小
  • 考虑对目标表进行分区
  • 考虑在批量加载期间删除索引

数据加载和性能指南的流程图很好地总结了: 在此处输入图像描述

正如其他人所说,您需要获得一些性能计数器来确定瓶颈的来源,因为您的实验表明 IO 可能不是限制。 数据加载和性能指南包括要监视的 SQL 等待类型和性能计数器的列表(文档中没有要链接的锚点,但这大约是文档中的 75%,在“优化批量加载”部分)

更新

我花了一段时间才找到链接,但Thomas Kejser 的这个 SQLBits 演讲也非常值得一看 -如果您没有时间观看整个内容,可以使用这些幻灯片。它重复了此处链接的一些材料,但还涵盖了有关如何处理特定性能计数器的高发生率的其他一些建议。

于 2012-06-23T06:43:30.020 回答
2

看来您已经做了很多,但我不确定您是否有机会研究 Alberto Ferrari SqlBulkCopy 性能分析报告,该报告描述了考虑与 SqlBulkCopy 相关的性能的几个因素。我想说那篇论文中讨论的很多事情仍然值得尝试,最好先尝试。

于 2012-06-23T01:39:34.607 回答
1

我不确定为什么你没有得到 100% 的 CPU、IO 或内存利用率。但是,如果您只是想提高批量加载速度,则需要考虑以下几点:

  1. 将您的数据文件划分为不同的文件。或者,如果它们来自不同的来源,则只需创建不同的数据文件。
  2. 然后同时运行多个批量插入。

根据您的情况,上述方法可能不可行;但如果可以的话,我相信它应该会提高你的加载速度。

于 2012-06-29T17:16:16.097 回答