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

时间:2014-07-22 00:56:00

标签: c# sql sql-server

我正在编写一个存储过程来将行插入表中。问题是,在某些操作中,我们可能希望插入超过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还是表类型变量

5 个答案:

答案 0 :(得分:7)

如果你更喜欢使用SQL,我认为你要找的是Bulk Insert

或者还有ADO.NET for Batch Operations选项,因此您将逻辑保留在C#应用程序中。 This article也非常完整。

<强>更新

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

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

数据库是Oracle,所以当然我们使用了Oracle的多行插入。事实证明,Java批量更新比Oracle的多值插入更快 (所谓的&#34;批量更新&#34;)。

我的建议是:

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

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

答案 1 :(得分:2)

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

不确定您使用的批处理方法,但您应该查看表值参数:Docs are here。你还想要批量写。

您还需要考虑服务器上的内存。批处理(一次说10K)可能会慢一些,但可能会降低服务器上的内存压力,因为您一次只能缓冲和处理一组。

  

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

另一个选项是bulk insert。 TVP受益于重复使用,因此它取决于您的使用模式。第一个链接有关于比较的说明:

  

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

     

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

此处的另一个比较:Performance of bcp/BULK INSERT vs. Table-Valued Parameters

答案 2 :(得分:0)

以下是我之前使用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;
    }
}

答案 3 :(得分:-2)

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

最终,你最终可能会遇到这样的事情:

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

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

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

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

答案 4 :(得分:-2)

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