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

我遇到了一些性能瓶颈与我的C#客户端插入批量数据到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,并且根据主键对值进行预先sorting。

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

它有帮助,如果我:

  1. 在我插入时删除主键,稍后重新创build
  2. 使用相同的模式插入到临时表中,并定期将它们传送到主表中,以保持插入发生的表的大小
  3. 还要别的吗?

– 基于我收到的答复,让我澄清一点:

Portman:我使用聚集索引,因为当数据全部导入时,我将需要按顺序访问数据。 在导入数据的时候,我并不特别需要索引。 在进行插入操作时使用非聚集PK索引有什么好处,而不是完全放弃导入的约束呢?

Chopeen:数据正在许多其他机器上远程生成(我的SQL服务器目前只能处理大约10个数据,但我希望能够添加更多)。 在本地机器上运行整个进程是不实际的,因为它将不得不处理50倍的input数据来生成输出。

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

你已经在使用SqlBulkCopy ,这是一个好的开始。

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

进一步阅读:

  • 在批量导入中最小化日志logging的先决条件
  • 优化批量导入性能

出于好奇,为什么你的索引是这样设置的? 看起来像ContainerId / BinId / Sequence更适合作为非聚簇索引。 是否有一个特定的原因,你想这个索引被聚类?

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

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

以下是一些可帮助您find解决scheme的资源:

一些批量加载速度比较

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

优化大容量复制性能

绝对看看NOCHECK和TABLOCK选项:

表提示(Transact-SQL)

INSERT(Transact-SQL)

我的猜测是,如果将该索引更改为非聚集 ,您将看到显着的改进。 这给你两个select:

  1. 将索引更改为非聚簇,并将其保留为堆表,而不使用聚簇索引
  2. 将索引更改为非聚簇,但添加一个代理键(如“id”),并使其成为标识,主键和聚簇索引

任何一个都会加快你的插入速度, 而不会显着减慢你的读取速度。

想想这样 – 现在,你告诉SQL做一个批量插入,但是你要求SQL为每个添加任何东西的表重新整理整个表。 使用非聚集索引,您将以任何顺序添加logging,然后构build一个单独的索引,指示其所需的顺序。

你尝试使用交易?

从你所描述的,让服务器提交100%的时间到磁盘,似乎你发送的每一行数据在primefacesSQL句子,从而迫使服务器提交(写入磁盘)每一行。

如果您使用事务,服务器将只在事务结束时提交一次

进一步的帮助:你用什么方法将数据插入服务器? 使用DataAdapter更新DataTable,还是使用string执行每个句子?

BCP – 这是一个痛苦的build立,但它已经在数据库的黎明,这是非常非常快。

除非你按照这个顺序插入数据,否则三部分索引会真的放慢速度。 稍后再应用也会让事情变得缓慢,但是会在第二步。

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

我不是一个聪明的人,而且我也没有太多的SqlClient.SqlBulkCopy方法的经验,但是我的2分钱是值得的。 我希望它能帮助你和其他人(或者至less让人们唤起我的无知)。

除非数据库数据文件(mdf)位于事务日志文件(ldf)的单独物理磁盘上,否则永远不会匹配原始文件拷贝速度。 此外,任何聚簇索引也需要在单独的物理磁盘上进行更公平的比较。

您的原始副本不会logging或维护用于索引目的的select字段(列)的sorting顺序。

我同意Portman创build一个非群集标识种子,并将现有的非群集索引更改为一个群集索引。

至于什么构造你在客户端上使用…(数据适配器,数据集,数据表等)。 如果服务器上的磁盘io为100%,我认为你的时间最好花在分析客户端结构上,因为它们看起来比服务器当前处理的要快。

如果按照波特曼关于最小日志logging的链接,我不会认为在交易中围绕你的批量拷贝将会有很多帮助,但是我一生中错过了很多次;)

这不一定会帮助你,但是如果你弄清楚你当前的问题,下一个评论可能会帮助你解决下一个瓶颈(networking吞吐量) – 特别是当它通过Internet时。

Chopeen也问了一个有趣的问题。 你如何确定使用300个logging数块插入? SQL Server有一个默认的数据包大小(我相信它是4096字节),这对我来说是有道理的,可以派生你的logging的大小,并确保你有效地使用客户端和服务器之间的数据包传输。 (注意,你可以改变你的客户端代码的数据包大小,而不是服务器选项,这会明显改变所有的服务器通信 – 可能不是一个好主意。)例如,如果你的logging大小为300logging批次,需要4500字节,你将发送2个数据包,第二个数据包大部分被浪费了。 如果批量logging计数是任意分配的,那么做一些快速简单的math可能是有意义的。

从我可以告诉的(并记住数据types的大小),每个logging(如果int = 4字节和smallint = 2个字节)恰好有20个字节。 如果你正在使用300个logging计数批次,那么你正试图发送300×20 = 6,000字节(加上我猜测连接等一些开销)。 您可以更有效地发送200个logging计数批次(200×20 = 4000 +空间开销)= 1个数据包。 再一次,你的瓶颈似乎仍然是服务器的磁盘io。

我意识到你正在比较一个原始的数据传输到SqlBulkCopy与相同的硬件/configuration,但这里是我会去的地方,如果挑战是我的:

这篇文章可能不会帮助你,因为它已经很老了,但是接下来我会问你的磁盘的RAIDconfiguration是什么以及你使用的是什么磁盘的速度? 尝试将日志文件放置在数据文件上使用RAID 5(理想情况下为1)的驱动器上。 这可以帮助减less大量的主轴移动到磁盘上的不同扇区,并导致更多的时间读取/写入,而不是非生产性的“移动”状态。 如果您已经将数据和日志文件分开,是否将索引放在与数据文件不同的物理磁盘驱动器上(只能使用聚簇索引执行此操作)。 这将不仅允许同时更新带有数据插入的日志信息,而且允许索引插入(以及任何昂贵的索引页操作)同时发生。

我认为这听起来像这可以使用SSIS包完成。 它们类似于SQL 2000的DTS包。 我已经使用它们成功转换了纯文本CSV文件,现有SQL表格甚至是来自跨越多个工作表的6位行的XLS文件的所有内容。 您可以使用C#将数据转换为可导入的格式(CSV,XLS等),然后让SQL服务器运行预定的SSIS作业来导入数据。

创build一个SSIS包相当容易,SQL Server的企业pipe理器工具中内置了一个向导(标记为“导入数据”,我认为),在向导的末尾可以select将其保存为SSIS包。 Technet上还有更多的信息。

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

是的,你的想法会有帮助。
如果在加载时没有发生读取,请依靠选项1。
如果在处理过程中正在查询目标表,则依靠选项2。

@安德鲁
题。 你插入300块。你插入的总量是多less? SQL服务器应该能够快速处理300个普通的旧插入。