在SQL Server(C#客户端)中批量插入大量数据的最快方法是什么

时间:2020-03-05 18:42:24  来源:igfitidea点击:

我的Cclient将大量数据插入SQL Server 2005数据库时遇到了一些性能瓶颈,并且我正在寻找加快过程的方法。

我已经在使用SqlClient.SqlBulkCopy(基于TDS)来加快通过电线的数据传输,这很有帮助,但是我仍然在寻找更多的东西。

我有一个简单的表,看起来像这样:

CREATE TABLE [BulkData](
 [ContainerId] [int] NOT NULL,
 [BinId] [smallint] NOT NULL,
 [Sequence] [smallint] NOT NULL,
 [ItemId] [int] NOT NULL,
 [Left] [smallint] NOT NULL,
 [Top] [smallint] NOT NULL,
 [Right] [smallint] NOT NULL,
 [Bottom] [smallint] NOT NULL,
 CONSTRAINT [PKBulkData] PRIMARY KEY CLUSTERED 
 (
  [ContainerIdId] ASC,
  [BinId] ASC,
  [Sequence] ASC
))

我将数据插入平均约300行的数据块中,其中每个数据块中的ContainerId和BinId是恒定的,并且Sequence值为0-n,并且这些值基于主键进行了预排序。

%Disk时间性能计数器在100%上花费大量时间,因此很明显磁盘IO是主要问题,但是我得到的速度比原始文件副本低几个数量级。

如果我有帮助,是否有帮助:

  • 插入时放下主键,稍后再创建
  • 插入具有相同架构的临时表,并定期将其转移到主表中,以保持发生插入的表的大小较小
  • 还要别的吗?

--
根据我得到的答复,让我澄清一下:

波特曼:我使用的是聚集索引,因为当数据全部导入后,我将需要按该顺序顺序访问数据。在导入数据时,我并不需要索引。与完全删除导入约束相比,在执行插入操作时具有非聚集PK索引有什么好处吗?

Chopeen:数据正在许多其他机器上远程生成(我的SQL Server当前只能处理大约10个,但是我希望能够添加更多)。在本地计算机上运行整个过程是不切实际的,因为这样一来,它必须处理50倍的输入数据才能生成输出。

Jason:在导入过程中,我没有对表进行任何并发查询,我将尝试删除主键,看看是否有帮助。

解决方案

回答

是的,想法会有所帮助。
如果在加载过程中没有读取事件,请选择选项1.
如果在处理过程中正在查询目标表,请选择选项2.

@安德鲁
问题。我们要插入的块数为300。插入的总数量是多少? SQL Server应该能够非常快速地处理300个普通的旧插入。

回答

如果可能,增加分配给服务器的内存或者服务器使用的缓冲区大小如何?

回答

我们是否尝试过使用交易?

从描述来看,让服务器将100%的时间提交到磁盘上,似乎我们正在使用原子SQL语句发送每行数据,从而迫使服务器每行提交(写入磁盘)。

如果改用事务,则服务器在事务结束时只会提交一次。

获得更多帮助:我们正在使用哪种方法将数据插入服务器?使用DataAdapter更新DataTable,还是使用字符串执行每个句子?

回答

我们已经在使用SqlBulkCopy,这是一个不错的开始。

但是,仅使用SqlBulkCopy类并不一定意味着SQL将执行批量复制。特别是,SQL Server执行高效批量插入必须满足一些要求。

进一步阅读:

  • 批量导入时最少记录的前提条件
  • 优化批量导入性能

出于好奇,为什么要这样设置索引?看来ContainerId / BinId / Sequence更适合作为非聚集索引。我们是否有特定的原因想要将该索引进行聚类?

回答

我认为这听起来可以使用SSIS包来完成。它们类似于SQL 2000的DTS包。我使用它们成功地转换了纯文本CSV文件,现有SQL表,甚至包括跨多个工作表的6位行的XLS文件的所有内容。我们可以使用C将数据转换为可导入的格式(CSV,XLS等),然后让SQL Server运行计划的SSIS作业来导入数据。

创建SSIS包非常容易,SQL Server的企业管理器工具内置了一个向导(我认为标记为"导入数据"),并且在向导的最后,我们可以选择将其另存为SSIS包。 Technet上还有很多其他信息。

回答

BCP的建立很麻烦,但是自DB诞生以来就已经存在,而且速度非常快。

除非我们按该顺序插入数据,否则三部分索引确实会减慢速度。稍后应用它也确实会减慢速度,但是将是第二步。

Sql中的复合键总是很慢,键越大,速度越慢。

回答

我的猜测是,如果将该索引更改为非聚集索引,将会看到巨大的改进。这给我们两个选择:

  • 将索引更改为非聚集索引,并将其保留为堆表,而不包含聚集索引
  • 将索引更改为非聚集索引,然后添加代理键(例如" id")并使其成为标识,主键和聚集索引

任一种都可以加快插入速度,而不会明显降低读取速度。

用这种方式考虑一下-现在,我们要告诉SQL进行批量插入,但是随后我们要SQL对每个添加了表的表重新排序整个表。使用非聚集索引,我们可以按记录的顺序添加记录,然后构建一个单独的索引以指示所需的顺序。

回答

我并不是一个聪明人,我对SqlClient.SqlBulkCopy方法没有太多的经验,但是这是我的2美分。我希望它能对我们和其他人有所帮助(或者至少使人们大声疾呼我的无知;)。

除非数据库数据文件(mdf)与事务日志文件(ldf)位于不同的物理磁盘上,否则我们将永远无法达到原始文件的复制速度。此外,任何群集索引也需要位于单独的物理磁盘上,以进行更公平的比较。

原始副本未记录或者维护用于索引目的的选择字段(列)的排序顺序。

我同意波特曼关于创建非聚集身份种子并将我们现有的非聚集索引更改为聚集索引的观点。

至于我们在客户端上使用的构造...(数据适配器,数据集,数据表等)。如果服务器上的磁盘io为100%,我认为分析客户端结构的时间最好不要花费在分析客户端结构上,因为它们似乎比服务器当前处理的速度更快。

如果我们遵循Portman的有关最小日志记录的链接,那么我认为将大容量副本放在事务中不会有什么帮助,但我一生中错了很多次;

这并不一定会立即对我们有所帮助,但是如果我们发现当前问题,那么下一条评论可能会帮助解决下一个瓶颈(网络吞吐量),特别是在通过Internet的情况下……

肖邦也问了一个有趣的问题。我们如何确定要使用300个记录计数块插入? SQL Server具有默认的数据包大小(我相信它是4096字节),对我来说,导出记录的大小并确保我们有效利用了在客户端和服务器之间传输的数据包是很有意义的。 (请注意,我们可以在客户端代码上更改数据包大小,而不是更改服务器选项,因为这显然对所有服务器通信都可能有所更改。)例如,如果记录大小导致300个记录批次需要4500字节,我们将发送2个数据包,而第二个数据包大部分被浪费了。如果批记录数量是任意分配的,那么进行一些简单的数学运算可能是有意义的。

据我所知(记住数据类型的大小),每个记录正好有20个字节(如果int = 4字节和smallint = 2字节)。如果我们正在使用300个记录计数批处理,那么我们将尝试发送300 x 20 = 6,000字节(另外,我猜想连接的开销会有所增加,等等)。我们可能更有效地以200个记录计数批次发送这些消息(200 x 20 = 4,000 +开销空间)= 1个数据包。再一次,瓶颈似乎仍然是服务器的磁盘io。

我意识到我们正在将原始数据传输与具有相同硬件/配置的SqlBulkCopy进行比较,但是如果挑战是我的,这也是我要去的地方:

这篇文章可能很老,可能不再对我们有帮助,但是接下来我想问一下磁盘的RAID配置是什么,以及我们使用的磁盘速度是多少?尝试将日志文件放在使用RAID 10且数据文件上使用RAID 5(最好是RAID 5)的驱动器上。这可以帮助减少大量的主轴移动到磁盘上的不同扇区,并导致更多的读取/写入时间,而不是非生产性的"移动"状态。如果我们已经将数据和日志文件分开,则索引是否与数据文件位于不同的物理磁盘驱动器上(只能使用聚集索引来做到这一点)。这不仅允许通过数据插入并发地更新日志记录信息,还可以允许索引插入(以及任何昂贵的索引页操作)同时发生。

回答

以下是在SQL Server中禁用/启用索引的方法:

--Disable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users DISABLE
GO
--Enable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users REBUILD

这里有一些资源可以找到解决方案:

一些批量加载速度比较

使用SqlBulkCopy将数据从客户端快速加载到SQL Server

优化批量复制性能

绝对要看看NOCHECK和TABLOCK选项:

表提示(Transact-SQL)

插入(Transact-SQL)