加速LINQ插入

时间:2009-08-25 14:33:02

标签: c# linq linq-to-sql ado.net

我有一个CSV文件,我必须将其插入SQL Server数据库。有没有办法加速LINQ插入?

我创建了一个简单的Repository方法来保存记录:

    public void SaveOffer(Offer offer)
    {
        Offer dbOffer = this.db.Offers.SingleOrDefault (
             o => o.offer_id == offer.offer_id);

        // add new offer
        if (dbOffer == null)
        {
            this.db.Offers.InsertOnSubmit(offer);
        }
        //update existing offer
        else
        {
            dbOffer = offer;
        }

        this.db.SubmitChanges();
    }

但是使用这种方法,程序比使用ADO.net SQL插入插入数据慢得多(新的SqlConnection,新的SqlCommand用于选择,如果存在,新的SqlCommand用于更新/插入)。

在100k csv行上,ADO.net方式需要大约一个小时,大约需要1分钟左右。对于2M csv行,ADO.net花了大约20分钟。 LINQ在25分钟内增加了大约30,000的2M行。我的数据库有3个表,在dbml中链接,但其他两个表都是空的。测试是在所有表格都空了的情况下进行的。

P.S。我曾尝试使用SqlBulkCopy,但我需要在将其插入数据库之前对Offer进行一些转换,我认为这违背了SqlBulkCopy的目的。

更新/编辑: 18小时后,LINQ版本只增加了大约200K行。

我也使用LINQ插件测试了导入,与ADO.net相比也非常慢。我没有看到插入/提交更改和选择/更新/插入/提交更改之间的重大区别。

我仍然需要尝试批量提交,手动连接到db和编译查询。

11 个答案:

答案 0 :(得分:19)

SubmitChanges不会批量更改,它会为每个对象执行一次插入语句。如果你想快速插入,我认为你需要停止使用LINQ。

当SubmitChanges正在执行时,启动SQL事件探查器并观察正在执行的SQL。

请参阅问题“LINQ to SQL可以执行批量更新和删除吗?或者它是否总是一次更新一行?”在这里:http://www.hookedonlinq.com/LINQToSQLFAQ.ashx

它链接到本文:http://www.aneyfamily.com/terryandann/post/2008/04/Batch-Updates-and-Deletes-with-LINQ-to-SQL.aspx,它使用扩展方法来修复linq无法批量插入和更新等。

答案 1 :(得分:7)

您是否尝试在事务中包装插入和/或延迟db.SubmitChanges以便批量插入几个?

事务通过减少对fsync()的需求来提高吞吐量,并且延迟db.SubmitChanges将减少.NET< - > db往返次数。

修改:有关更多优化原则,请参阅http://www.sidarok.com/web/blog/content/2008/05/02/10-tips-to-improve-your-linq-to-sql-application-performance.html

答案 2 :(得分:6)

查看以下页面,了解如何更改代码以使用批量插入而不是使用LINQ的 InsertOnSubmit()函数。

您只需将(提供的) BulkInsert 类添加到代码中,对代码进行一些细微更改,您就会看到性能的巨大提升。

Mikes Knowledge Base - BulkInserts with LINQ

祝你好运!

答案 3 :(得分:4)

我想知道您是否在数据上下文中累积了过多的数据集,这使得针对内部标识缓存(在SingleOrDefault期间检查过一次的行进行解析很慢) “未命中”我预计在实体实现时会看到第二次打击。)

我不记得100%短路是否适用于SingleOrDefault(尽管it will in .NET 4.0)。

对于某些 n 的每个 n 操作,我会尝试放弃数据上下文(提交更改并替换为空的) - 可能是250或其他。


鉴于您此刻正在调用SubmitChanges isntance ,您可能也会浪费大量时间检查增量 - 如果您只更改了一行,则无意义。只批量调用SubmitChanges;不是每条记录。

答案 4 :(得分:4)

亚历克斯给出了最好的答案,但我认为有些事情正在被忽视。

您在这里遇到的主要瓶颈之一是分别为每个项目调用SubmitChanges。我认为大多数人都不知道的一个问题是,如果您没有自己手动打开DataContext的连接,那么DataContext将自己重复打开和关闭它。但是,如果你自己打开它,然后在你完成时自己关闭它,事情会运行得更快,因为它不必每次都重新连接到数据库。当我试图找出为什么DataContext.ExecuteCommand()在一次执行多个命令时如此令人难以置信地缓慢时,我发现了这一点。

其他一些可以加快速度的领域:

虽然Linq To SQL不支持您的直接批处理,但您应该等到调用SubmitChanges(),直到您首先分析了所有内容。在每次InsertOnSubmit调用之后,您不需要调用SubmitChanges()。

如果实时数据完整性不是非常重要,您可以在开始检查商品是否已存在之前从服务器检索offer_id列表。这可能会显着减少您调用服务器以获取现有项目的次数。

答案 5 :(得分:3)

为什么不将offer []传递给该方法,并在将缓存提交到数据库之前执行缓存中的所有更改。或者您可以使用组进行提交,因此您不会用完缓存。最重要的是你发送数据需要多长时间,浪费的最大时间是关闭和打开连接。

答案 6 :(得分:2)

将此转换为已编译的查询是我能想到的最简单的方法来提升您的绩效:

更改以下内容:

    Offer dbOffer = this.db.Offers.SingleOrDefault (
         o => o.offer_id == offer.offer_id);

为:

Offer dbOffer = RetrieveOffer(offer.offer_id);

private static readonly Func<DataContext, int> RetrieveOffer
{
   CompiledQuery.Compile((DataContext context, int offerId) => context.Offers.SingleOrDefault(o => o.offer_id == offerid))
}

单独进行此更改不会像ado.net版本那样快,但这将是一项重大改进,因为如果没有编译查询,则每次运行此方法时都会动态构建表达式树。

正如已经提到过的一张海报,您必须重构代码,以便在您希望获得最佳性能时仅调用一次提交更改。

答案 7 :(得分:2)

在将记录插入数据库之前,您是否真的需要检查记录是否存在。我认为它看起来很奇怪,因为数据来自csv文件。

  

P.S。我试过使用SqlBulkCopy,   但我需要做一些改造   在提供之前将其插入到   db,我认为打败了   SqlBulkCopy的目的。

我认为它根本没有打败目的,为什么会这样呢?只需用csv中的所有数据填充一个简单的数据集,然后执行SqlBulkCopy。我做了类似的事情,收集了30000多行,导入时间从几分钟到几秒

答案 8 :(得分:1)

我怀疑这不是花费很长时间的插入或更新操作,而是确定您的优惠是否已存在的代码:

Offer dbOffer = this.db.Offers.SingleOrDefault (
         o => o.offer_id == offer.offer_id);

如果你想优化这一点,我想你会走上正轨。也许使用秒表课做一些有助于证明我对错的时机。

通常,在不使用Linq-to-Sql时,您将拥有一个插入/更新过程或sql脚本,用于确定您传递的记录是否已存在。你在Linq做了这个昂贵的操作,这肯定永远不会希望匹配原生sql的速度(当你使用SqlCommand时会发生这种情况并选择是否存在记录)查找主键。

答案 9 :(得分:0)

您必须了解linq为您执行的所有ADO操作动态创建代码,而不是手写,因此它总是比您的手动代码占用更多时间。它只是编写代码的简单方法,但如果你想谈论性能,ADO.NET代码总是会更快,这取决于你如何编写代码。

我不知道linq是否会尝试重复使用它的最后一个语句,如果确实如此,那么使用更新批处理分离插入批处理可能会略微提高性能。

答案 10 :(得分:0)

此代码运行正常,可防止大量数据:

if (repository2.GeoItems.GetChangeSet().Inserts.Count > 1000)
{
    repository2.GeoItems.SubmitChanges();
}

然后,在批量插入结束时,使用:

repository2.GeoItems.SubmitChanges();