【问题标题】:LINQ fastest way to update lots of records (>2m)LINQ 更新大量记录的最快方法 (>2m)
【发布时间】:2013-06-04 16:03:45
【问题描述】:

我有这个循环:

using(var db = new MainContext())
{
    var q = db.tblInternalURLs;
    foreach (var rec in q)
    {
        db.ExecuteCommand("UPDATE tblInternalURLS SET hash = '" + LoginAPI.GetSha1(rec.URL) + "' WHERE ID = " + rec.ID);
    }
}

将更新查询转换为db.ExecuteCommand 大大提高了速度,但是我想知道是否有更快的方法来执行这些查询,因为超过 2,000,000 条记录仍然需要很长时间。我相信很多开销都在最初的 LINQ 查询中。这是正确的吗?

【问题讨论】:

  • 这取决于tblInternalURLs 是什么。如果可以用一条sql命令写,可以,可以改进,否则不行。
  • 你可能会用更快的 select 子句替换 var q = db.tblInternalURLs; ... 看看 hivemind 的想法会很有趣...
  • 如果这只是对 sql server 表的迭代,UPDATE tblInternalURLS SET hash = hashbytes('sha1', url)
  • @Xander 这就是为什么它是评论,而不是答案。它非常适合作为评论,非常值得做。
  • @Servy 我希望这个问题不会因此而被否决......

标签: c# asp.net sql linq


【解决方案1】:

好吧,既然 SQL Server 支持散列,您可以通过编写 SQL 查询一次性完成整个表来避免将任何数据带到客户端:

update 
 tblInternalURLS 
SET 
 hash = HASHBYTES('SHA1',CONVERT(nvarchar(4000), URL))

如果哈希存储为字符串,sys.fn_varbintohexsubstring 可能会很方便。

【讨论】:

  • 值得明确指出,这种方法无需在程序中完全迭代 2mm 行
  • 这必须运行得更快,但更有可能死锁。
  • @Jodrell,我想请你解释一下这如何导致死锁
  • @gunr2171 为什么需要where 声明? OP 正在迭代整个表并为 每一 行生成更新。因此,我的答案中的update 不需要约束。
  • @gunr2171,也许不是死锁,但肯定是冲突。如果我在 1 个事务中一次更改所有 2x10^6 记录,而其他一些事务想要在该处理期间更改一个或多个哈希值,则会发生冲突。如果您一次提交一行或一页更改,则不太可能发生冲突。所以有一个权衡,但唯一相关的是任何哈希值都可能同时发生变化。
【解决方案2】:

以下应该更快,因为它将select 限制为仅返回所需的列。

变化:

var q = db.tblInternalURLs;

收件人:

var q = db.tblInternalURLs.Select(x => new { URL = x.URL, ID = x.ID }).ToList();

【讨论】:

    【解决方案3】:

    我建议对您的查询进行分页。现在,您正在一次提取所有 2,000,000 条记录。这会消耗数据库、网络连接、客户端内存等。

    通过将其分解为几个较小的查询,每个查询可以抓取几千页,您可能会看到一些明显的改进。

    这里有一些帮助对给定查询进行分页:

    public static IEnumerable<T> Paginate<T>(this IQueryable<T> query, int pageSize)
    {
        return GetPages(query, pageSize).SelectMany(x => x);
    }
    
    public static IEnumerable<IEnumerable<T>> GetPages<T>(this IQueryable<T> query, int pageSize)
    {
        for (int currentPage = 0; true; currentPage++)
        {
            IEnumerable<T> nextPage = query.Skip(currentPage * pageSize)
                .Take(pageSize)
                .ToList();
    
            if (nextPage.Any())
                yield return nextPage;
            else
                yield break;
        }
    }
    

    如果您在查询中添加对Paginate(1000) 的调用,您至少应该会看到一些改进。

    【讨论】:

      【解决方案4】:

      更快的方法是使用本机 ADO.NET Prepare 命令,然后绑定参数而不是 concat 查询字符串并生成许多不同的查询(从数据库的角度来看)。 每个新查询都必须由服务器解析...

      这里是sn-p

      var conn = ...//get native connection from your context
      var cmd = conn.CreateCommand();
      cmd.CommandText = "UPDATE tblInternalURLS SET hash = @hash WHERE ID = @id";
      
      var hashParam = cmd.CreateParameter();
      //set parameter type and name
      
       var idParam = cmd.CreateParameter();
      //set parameter type and name
      
      cmd.Parameters.Add(hashParam);
      cmd.Parameters.Add(idParam);
      
      //prepare command
      cmd.Prepare();
      
       foreach (var rec in q)
       {
           idParam.Value = rec.ID;
           hashParam.Value =  LoginAPI.GetSha1(rec.URL);
           cmd.ExecuteNonQuery();
      
       } 
      

      更新
      如果您使用 SQL Server 并且哈希列必须始终与 URL 同步,那么您可以修改 tblInternalURLS 表并将哈希列转换为 computed 列。在这种情况下,哈希列将始终与 URL 同步。

      ALTER TABLE dbo.tblInternalURLS DROP COLUMN hash
      
      ALTER TABLE dbo.tblInternalURLS 
       ADD hash AS 
       CAST(HASHBYTES('SHA1', URL) AS VARBINARY(20)) PERSISTED
      

      【讨论】:

      • 您可能希望将 conn 对象转换为 using 以便它自动关闭并自行处理。
      • 我相信连接将被外部 linq 上下文关闭/返回到池中。但在其他情况下,我会通过 using 来包装 conn
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-06-02
      • 1970-01-01
      • 2014-05-12
      • 2011-02-15
      • 1970-01-01
      • 1970-01-01
      • 2013-01-02
      相关资源
      最近更新 更多