在SQL Server中插入100万行的最快方法

我正在编写一个存储过程来将行插入表中。 问题是,在某些操作中,我们可能希望插入超过100万行,并且我们希望快速实现。 另一件事是在其中一个列中,它是Nvarchar(MAX) 。 我们可能希望在此列中放置平均1000个字符。

首先,我写了一个prc来逐行插入。 然后我生成一些随机数据用于插入, NVARCHAR(MAX) 1000个字符的字符串。 然后使用循环调用prc来插入行。 如果我使用SQL服务器登录要插入的数据库服务器,则perf非常糟糕,需要48分钟。 如果我使用C#连接到我桌面上的服务器(这是我们通常想做的事情),则需要大约90分钟。

然后,我更改了prc以获取表类型参数作为输入。 我以某种方式准备了行并将它们放在表类型参数中,并通过以下命令执行插入:

 INSERT INTO tableA SELECT * from @tableTypeParameterB 

我尝试批量大小为1000行和3000行(在@tableTypeParameterB中放入1000-3000行以插入一次)。 表现仍然不好。 如果我在SQL服务器中运行它需要大约3分钟来插入100万行,如果我使用C#程序从我的桌面连接则需要大约10分钟。

tableA有一个包含2列的聚簇索引。

我的目标是尽可能快地插入(我的想法目标是在1分钟内)。 有没有办法优化它?


只是一个更新:

我尝试了下面的一些人建议的批量复制插入。 我尝试使用SQLBULKCOPY一次插入1000行和10000行。 插入100万行的性能仍然是10分钟(每行有一个1000字符的列)。 没有性能提升。 还有其他建议吗?


基于评论的更新需要。

数据实际上来自UI。 用户将更改使用UI以批量选择,我们说,一百万行,并将一列从旧值更改为新值。 此操作将在单独的过程中完成。但是,我们需要做的是使中间层服务从UI获取旧值和新值并将其插入表中。 旧值和新值最多可包含4000个字符,平均值为1000个字符。 我认为长字符串旧/新值会降低速度,因为当我将测试数据旧值/新值更改为20-50个字符并且插入非常快时无论使用SQLBulkCopy还是表类型变量

如果您更喜欢使用SQL,我认为您正在寻找的是批量插入 。

或者还有ADO.NET for Batch Operations选项,因此您可以将逻辑保留在C#应用程序中。 这篇文章也很完整。

更新

是的,我担心批量插入只能用于导入的文件(来自数据库内)。

我有一个Java项目的经验,我们需要插入数百万行(数据来自应用程序外部btw)。

数据库是Oracle,所以当然我们使用了Oracle的多行插入。 事实certificate,Java批量更新比Oracle的多值插入(所谓的“批量更新”)快得多。

我的建议是:

  • 使用ADO.NET批量插入比较SQL Server代码的多值插入 (然后您可以从数据库内部读取,如果您愿意,可以读取过程)之间的性能。

如果您要操作的数据来自应用程序外部(如果它不在数据库中),我会说只是去ADO.NET批量插入。 我认为这是你的情况。

注意:请记住,批量插入通常使用相同的查询。 这就是他们如此之快的原因。

在循环中调用prc会导致多次往返SQL。

不确定您使用的批处理方法,但您应该查看表值参数: 文档在这里 。 你还想要批量写。

您还需要考虑服务器上的内存。 批处理(一次说10K)可能会慢一点,但是由于您一次缓冲并处理一组,因此可能会降低服务器上的内存压力。

表值参数提供了一种简单的方法,可以将多行数据从客户端应用程序封送到SQL Server,而无需多次往返或特殊的服务器端逻辑来处理数据。 您可以使用表值参数来封装客户端应用程序中的数据行,并使用单个参数化命令将数据发送到服务器。 传入的数据行存储在表变量中,然后可以使用Transact-SQL对其进行操作。

另一种选择是批量插入 。 TVP受益于重复使用,因此它取决于您的使用模式。 第一个链接有关于比较的注释:

使用表值参数与使用基于集合的变量的其他方式相当; 但是,对于大型数据集,经常使用表值参数会更快。 与具有比表值参数更大的启动成本的批量操作相比,表值参数在插入少于1000行时表现良好。

重用的表值参数受益于临时表缓存。 与等效的BULK INSERT操作相比,此表缓存可实现更好的可伸缩性。

这里的另一个比较: bcp / BULK INSERT与表值参数的性能

这是我之前使用SqlBulkCopy的一个例子。 授予它我只处理了大约10,000条记录,但它在查询运行几秒钟之后插入了它们。 我的字段名称是相同的,所以很容易。 您可能必须修改DataTable字段名称。 希望这可以帮助。

 private void UpdateMemberRecords(Int32 memberId) { string sql = string.Format("select * from Member where mem_id > {0}", memberId); try { DataTable dt = new DataTable(); using (SqlDataAdapter da = new SqlDataAdapter(new SqlCommand(sql, _sourceDb))) { da.Fill(dt); } Console.WriteLine("Member Count: {0}", dt.Rows.Count); using (SqlBulkCopy sqlBulk = new SqlBulkCopy(ConfigurationManager.AppSettings("DestDb"), SqlBulkCopyOptions.KeepIdentity)) { sqlBulk.BulkCopyTimeout = 600; sqlBulk.DestinationTableName = "Member"; sqlBulk.WriteToServer(dt); } } catch (Exception ex) { throw; } } 

根据您的最终目标,查看entity framework(或类似)可能是个好主意。 这抽象出了SQL,因此您不必在客户端应用程序中担心它,这应该是应该如何。

最终,你最终会得到这样的东西:

 using (DatabaseContext db = new DatabaseContext()) { for (int i = 0; i < 1000000; i++) { db.Table.Add(new Row(){ /* column data goes here */}); } db.SaveChanges(); } 

这里的关键部分(归结为许多其他答案)是entity framework处理构建实际的插入语句并将其提交到数据库。

在上面的代码中,在调用SaveChanges然后发送所有内容之前,实际上不会向数据库发送任何内容。

我不太记得我发现它的位置,但有研究表明,每SaveChanges调用一次SaveChanges是值得的。 从内存来看,我认为每1000个条目都是提交数据库的不错选择。 与每100个条目相比,提交每个条目不会提供太多的性能优势,10000会超过限制。 不要相信我的话,数字可能是错的。 你似乎对事物的测试方面有很好的把握,所以要玩弄东西。

如果你有SQL2014,那么内存中OLTP的速度是惊人的; http://msdn.microsoft.com/en-au/library/dn133186.aspx