【问题标题】:Why does my SQL update for 20.000 records take over 5 minutes?为什么我的 20.000 条记录的 SQL 更新需要超过 5 分钟?
【发布时间】:2021-06-17 22:16:23
【问题描述】:

我有一段 C# 代码,它更新了本地主机数据库中 ~1000x20 记录的两个特定列。据我所知(虽然我离成为数据库专家还差得很远),应该不会花很长时间,但需要超过 5 分钟。

我尝试了 SQL 事务,但没有成功。 SqlBulkCopy 似乎有点矫枉过正,因为它是一个有几十列的大表,我只需要为一组记录更新 1/2 列,所以我想保持简单。有没有更好的方法来提高效率?

代码本身:

public static bool UpdatePlayers(List<Match> matches)
    {
        using (var connection = new SqlConnection(Database.myConnectionString))
        {
            connection.Open();
            SqlCommand cmd = connection.CreateCommand();

            foreach (Match m in matches)
            {
                cmd.CommandText = "";
                foreach (Player p in m.Players)
                {
                    // Some player specific calculation, which takes almost no time.
                    p.Morale = SomeSpecificCalculationWhichMilisecond();
                    p.Condition = SomeSpecificCalculationWhichMilisecond();


                    cmd.CommandText += "UPDATE [Players] SET [Morale] = @morale, [Condition] = @condition WHERE [ID] = @id;";
                    cmd.Parameters.AddWithValue("@morale", p.Morale);
                    cmd.Parameters.AddWithValue("@condition", p.Condition);
                    cmd.Parameters.AddWithValue("@id", p.ID);
                }
                cmd.ExecuteNonQuery();
            }
        }
        return true;
    }

【问题讨论】:

  • 您使用的是哪个 dbms?
  • 看起来您正在添加数千个参数并且没有清除该 CommandText,因为您一直在附加它。
  • 您在循环中一次更新 n 行 1。如果性能是您的目标,您应该考虑以基于集合的方式进行一次更新,这在 20,000 行(不是记录)上可能需要几秒钟。
  • 你永远不会清除参数集合,所以你一直在添加它。
  • 您的代码在每次更新时都执行一次提交,这是 .NET SQL 库的默认事务处理,因此在每次 UPDATE 提交期间肯定会有延迟。我建议在事务中运行记录组,这样会更快。

标签: c# sql sql-server


【解决方案1】:

一次更新 20,000 条记录是一个缓慢的过程,因此需要 5 分钟以上的时间。

根据您的查询,我建议将数据放入 临时表,然后将临时表加入更新。这样它只需要扫描表来更新一次,然后更新所有的值。

注意:如果您正在更新的字段上有索引和/或表中有大量数据,则更新可能仍需要一段时间。

更新查询示例:

    UPDATE P
    SET [Morale] = TT.[Morale], [Condition] = TT.[Condition]
    FROM        [Players] AS P
    INNER JOIN  #TempTable AS TT ON TT.[ID] = P.[ID];

填充临时表

如何将数据放入临时表取决于您。我怀疑您可以使用SqlBulkCopy,但您可能必须将其放入实际表中,然后在完成后删除该表。

如果可能,我建议在临时表的 ID 列上放置一个 主键。这可能会加快更新过程,因为它可以更快地在临时表中找到相关的 ID

【讨论】:

  • 忽略最后一条评论,我刚刚看了你的最后一段。 +1
  • 希望你不介意,但我做了一些小的格式更改
  • @MickyD 看到了。看起来它更容易阅读,所以我很好。 dodo 使用句点作为千位分隔符,这就是我省略逗号的原因。但它仍然应该是可读的,特别是因为大多数 SO 用户使用逗号作为千位分隔符。以防万一。
【解决方案2】:

小改进;

  • 为命令文本使用字符串生成器
  • 确保您的参数名称实际上是唯一的
  • 清除您的参数以供下次使用
  • 根据每场比赛的玩家人数,将 N 个命令一起批处理,而不是 1 场比赛。

更大的改进;

  • 使用表值作为参数和合并sql语句。应该看起来像这样(未经测试);
CREATE TYPE [MoraleUpdate] AS TABLE (
    [Id] ...,
    [Condition] ...,
    [Morale] ...
)
GO
MERGE [dbo].[Players] AS [Target]
USING @Updates AS [Source]
ON [Target].[Id] = [Source].[Id]

WHEN MATCHED THEN
    UPDATE SET SET [Morale] = [Source].[Morale],
        [Condition]  = [Source].[Condition]
DataTable dt = new DataTable();
dt.Columns.Add("Id", typeof(...));
dt.Columns.Add("Morale", typeof(...));
dt.Columns.Add("Condition", typeof(...));

foreach(...){
    dt.Rows.Add(p.Id, p.Morale, p.Condition);
}

SqlParameter sqlParam = cmd.Parameters.AddWithValue("@Updates", dt);
sqlParam.SqlDbType = SqlDbType.Structured;
sqlParam.TypeName = "dbo.[MoraleUpdate]";

cmd.ExecuteNonQuery();

您还可以在计算值时实现一个 DbDatareader 以 stream 将值发送到服务器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-10
    • 2019-03-10
    • 1970-01-01
    • 2010-12-11
    相关资源
    最近更新 更多