C# LINQ更新大量记录的最快方法(>;2m)

C# LINQ更新大量记录的最快方法(>;2m),c#,asp.net,sql,linq,C#,Asp.net,Sql,Linq,我有一个循环: 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.

我有一个循环:

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大大提高了速度,但是我想知道是否有更快的方法来执行这些查询,因为超过2000000条记录仍然需要很长时间。我相信很多开销都在初始LINQ查询中。这是否正确?

以下操作应该更快,因为它将
选择限制为仅返回所需的列

更改:

var q = db.tblInternalURLs;
致:


鉴于SQL Server支持哈希,您可以通过编写SQL查询一次性完成整个表,从而避免将任何数据带到客户端:

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

如果散列存储为字符串,
sys.fn\u varbintohexsubstring
可能很方便。

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

下面是片段

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表并将哈希列转换为计算列。在这种情况下,哈希列将始终与URL同步

ALTER TABLE dbo.tblInternalURLS DROP COLUMN hash

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

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

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

以下是一些帮助程序,可以为给定查询分页:

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;
    }
}
公共静态IEnumerable分页(此IQueryable查询,int pageSize)
{
返回GetPages(查询,页面大小)。选择many(x=>x);
}
公共静态IEnumerable GetPages(此IQueryable查询,int pageSize)
{
对于(int currentPage=0;true;currentPage++)
{
IEnumerable nextPage=query.Skip(当前页面*页面大小)
.Take(页面大小)
.ToList();
if(nextPage.Any())
下一页收益率;
其他的
屈服断裂;
}
}

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

这取决于
tblinternals
是什么。如果您可以用一个SQL命令来编写它,那么是的,它可以得到改进,否则就不会了。您可以替换
var q=db.tblinternals带有更快的select子句。。。看看hivemind的想法会很有趣……如果这只是sql server表上的一次迭代,
UPDATE tblInternalURLS SET hash=hashbytes('sha1',url)
@Xander这就是为什么它是一个注释,而不是答案。这是一个非常恰当的评论,非常值得一提。@Servy我希望这个问题不会因为它而被否决……值得明确指出的是,这种方法消除了在程序中迭代2mm行的需要,但总体上运行速度要快得多,但更有可能死锁。@Jodrell,我想请你解释一下这怎么会导致deadlock@gunr2171为什么需要
where
语句?OP迭代整个表并为每一行生成更新。因此,我的答案中的更新不需要约束。@gunr217,可能不是死锁,但肯定是冲突。如果我在一个事务中一次更改所有2x10^6记录,而另一个事务希望在该处理过程中更改一个或多个哈希值,则会发生冲突。如果一次提交一行或一页更改,则冲突的可能性较小。因此有一个折衷办法,但唯一相关的是,任何哈希值都可能同时发生更改。您可能希望使用
conn
对象转换为
,以便它自动关闭并自行处理。我确信,连接将通过外部linq上下文关闭/返回到池中。但在另一种情况下,我会使用。
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;
    }
}