在 SQL Server(C# 客户端)中批量插入大量数据的最快方法是啥
Posted
技术标签:
【中文标题】在 SQL Server(C# 客户端)中批量插入大量数据的最快方法是啥【英文标题】:What's the fastest way to bulk insert a lot of data in SQL Server (C# client)在 SQL Server(C# 客户端)中批量插入大量数据的最快方法是什么 【发布时间】:2010-09-06 15:43:48 【问题描述】:我的 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 在每个块中是恒定的,并且序列值是 0-n,并且这些值是根据主键预先排序的。
%Disk time 性能计数器在 100% 上花费了大量时间,因此很明显磁盘 IO 是主要问题,但我得到的速度比原始文件副本低几个数量级。
如果我这样做有帮助吗:
-
在我进行插入时删除主键并稍后重新创建它
插入到具有相同架构的临时表中,并定期将它们转移到主表中,以保持发生插入的表的大小较小
还有别的吗?
-- 根据我得到的回复,让我稍微澄清一下:
Portman:我正在使用聚集索引,因为当数据全部导入后,我需要按顺序依次访问数据。导入数据时,我并不特别需要索引。在进行插入时使用非聚集 PK 索引而不是完全删除约束以进行导入有什么优势吗?
Chopeen:数据是在许多其他机器上远程生成的(我的 SQL 服务器目前只能处理大约 10 个,但我希望能够添加更多)。在本地机器上运行整个过程是不切实际的,因为它必须处理 50 倍的输入数据才能生成输出。
Jason:我在导入过程中没有对表进行任何并发查询,我会尝试删除主键,看看是否有帮助。
【问题讨论】:
msdn.microsoft.com/en-us/library/ms174335.aspx 【参考方案1】:以下是在 SQL Server 中禁用/启用索引的方法:
--Disable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users DISABLE
GO
--Enable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users REBUILD
这里有一些资源可以帮助您找到解决方案:
Some bulk loading speed comparisons
Use SqlBulkCopy to Quickly Load Data from your Client to SQL Server
Optimizing Bulk Copy Performance
一定要看看 NOCHECK 和 TABLOCK 选项:
Table Hints (Transact-SQL)
INSERT (Transact-SQL)
【讨论】:
此线程上的信息可能会很有帮助dba.stackexchange.com/questions/30734/…【参考方案2】:您已经在使用SqlBulkCopy,这是一个好的开始。
但是,仅仅使用 SqlBulkCopy 类并不一定意味着 SQL 会执行批量复制。特别是,SQL Server 必须满足一些要求才能执行高效的批量插入。
进一步阅读:
Prerequisites for Minimal Logging in Bulk Import Optimizing Bulk Import Performance出于好奇,您的索引为什么会这样设置? ContainerId/BinId/Sequence 似乎非常更适合作为非聚集索引。您希望这个索引聚集在一起有什么特别的原因吗?
【讨论】:
【参考方案3】:我的猜测是,如果您将该索引更改为非集群,您会看到显着的改进。这为您提供了两种选择:
-
将索引更改为非聚集索引,并将其保留为堆表,没有聚集索引
将索引更改为非聚集索引,然后添加代理键(如“id”)并使其成为标识、主键和聚集索引
其中任何一个都会加快您的插入速度而不会显着减慢您的阅读速度。
这样想——现在,您告诉 SQL 进行批量插入,但随后您要求 SQL 对您添加任何内容的每个表重新排序。使用非聚集索引,您将按照它们进入的任何顺序添加记录,然后构建一个单独的索引来指示它们所需的顺序。
【讨论】:
【参考方案4】:您是否尝试过使用交易?
根据您的描述,让服务器将 100% 的时间提交到磁盘,您似乎正在以原子 SQL 语句发送每一行数据,从而迫使服务器每行提交(写入磁盘)。
如果您改用事务,服务器只会在事务结束时提交一次。
更多帮助:您使用什么方法向服务器插入数据?使用 DataAdapter 更新 DataTable,还是使用字符串执行每个句子?
【讨论】:
很晚了,但对于现在发现这个的其他人来说,这是一件好事。我正在使用来自客户端应用程序的通用 DbCommand 代码编写插入过程,因此我不能使用 SqlClient 特定的东西或 SQL Server 批量工具 - 这个简单的提示已将我的运行时间从一分半缩短到 5 秒。 是的,我在我的项目中使用了它,它比批量插入更高效。你能帮我理解为什么 BulkInsert 比使用交易慢吗? (我假设批量插入将在一批行的批量插入完成后提交)【参考方案5】:BCP - 设置起来很痛苦,但它从 DB 诞生之初就已经存在,而且速度非常快。
除非您按该顺序插入数据,否则 3 部分索引会真正减慢速度。稍后应用它也确实会减慢速度,但需要第二步。
Sql中的复合键总是很慢,键越大越慢。
【讨论】:
【参考方案6】:我并不是一个真正聪明的人,我对 SqlClient.SqlBulkCopy 方法没有太多经验,但这是我的 2 美分,物有所值。我希望它可以帮助你和其他人(或者至少让人们说出我的无知;)。
除非您的数据库数据文件 (mdf) 位于与事务日志文件 (ldf) 不同的物理磁盘上,否则您将永远无法匹配原始文件的复制速度。此外,任何聚集索引也需要位于单独的物理磁盘上,以便进行更公平的比较。
您的原始副本没有记录或维护选择字段(列)的排序顺序以用于索引目的。
我同意 Portman 关于创建非聚集身份种子并将您现有的非聚集索引更改为聚集索引的观点。
至于您在客户端上使用的构造...(数据适配器、数据集、数据表等)。如果服务器上的磁盘 io 为 100%,我认为最好不要花时间分析客户端构造,因为它们似乎比服务器当前可以处理的速度更快。
如果您关注 Portman 关于最小日志记录的链接,我认为在事务中围绕您的批量复制不会有很大帮助,但我在我的生活中犯过很多次错误;)
这不一定对你现在有帮助,但如果你弄清楚你当前的问题,下一条评论可能有助于解决下一个瓶颈(网络吞吐量) - 特别是如果它通过 Internet...
Chopeen 也问了一个有趣的问题。您如何确定使用 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(理想情况下为 1)的驱动器上。这可以帮助减少到磁盘上不同扇区的大量主轴移动,并导致更多时间读/写,而不是非生产性的“移动”状态。如果您已经将数据和日志文件分开,您的索引是否与数据文件位于不同的物理磁盘驱动器上(您只能使用聚簇索引执行此操作)。这不仅允许在插入数据的同时更新日志信息,而且允许同时进行索引插入(以及任何代价高昂的索引页操作)。
【讨论】:
【参考方案7】:我认为这听起来可以使用SSIS packages 来完成。它们类似于 SQL 2000 的 DTS 包。我已经使用它们成功地转换了纯文本 CSV 文件、现有 SQL 表,甚至是跨多个工作表的 6 位行的 XLS 文件。您可以使用 C# 将数据转换为可导入的格式(CSV、XLS 等),然后让您的 SQL 服务器运行计划的 SSIS 作业来导入数据。
创建 SSIS 包非常容易,SQL Server 的企业管理器工具中内置了一个向导(我认为标记为“导入数据”),在向导结束时,您可以选择将其保存为SSIS 包。还有更多信息on Technet。
【讨论】:
【参考方案8】:是的,您的想法会有所帮助。 如果在加载时没有发生读取,请使用选项 1。 如果在处理过程中查询目标表,请使用选项 2。
@安德鲁 题。您插入 300 个块。您插入的总金额是多少? SQL 服务器应该能够非常快速地处理 300 个普通的旧插入。
【讨论】:
以上是关于在 SQL Server(C# 客户端)中批量插入大量数据的最快方法是啥的主要内容,如果未能解决你的问题,请参考以下文章
在 SQL Server 中批量插入部分引用的 CSV 文件