【问题标题】:Why code is maxing out the CPU while querying the database?为什么代码在查询数据库时会耗尽 CPU?
【发布时间】:2012-11-17 16:53:59
【问题描述】:

下面的 C# 代码检查 SQL 数据库以查看记录是否与 ClientID 和用户名匹配。如果找到超过 15 条或更多匹配的记录,则我的 Windows 2008 服务器上的 CPU 峰值约为 78%,而在执行以下 C# 代码时会找到 15 条记录。 SQL Server 2008 数据库和软件位于另一台服务器上,因此问题不在于 SQL Server 使 CPU 达到峰值。问题在于我的 C# 软件正在执行下面的代码。在执行数据库查询并找到记录时,我可以看到包含以下 C# 代码的软件可执行文件飙升至 78%。

有人可以告诉我,当找到 15 个或更多匹配记录时,我的代码是否有问题导致 CPU 出现峰值?你能告诉我/告诉我如何优化我的代码吗?

更新:如果它找到 10 条记录,CPU 只会以 2-3% 的速度飙升。只有在找到 15 条或更多记录时,CPU 才会以 78% 的速度峰值持续 2 到 3 秒。

//ClientID[0] will contain a ClientID of 10 characters
//output[0] will contain a User Name
char[] trimChars = { ' ' };
using (var connection = new SqlConnection(string.Format(GlobalClass.SQLConnectionString, "History")))
{
    connection.Open();
    using (var command = new SqlCommand())
    {
        command.CommandText = string.Format(@"SELECT Count(*) FROM Filelist WHERE [ToAccountName] = '" + output[0] + @"'");
        command.Connection = connection;
        var rows = (int) command.ExecuteScalar();
        if (rows >= 0)
        {
            command.CommandText = string.Format(@"SELECT * FROM Filelist WHERE [ToAccountName] = '" + output[0] + @"'");
            using (SqlDataReader reader = command.ExecuteReader())
            {
                if (reader.HasRows)
                {
                    while (reader.Read())
                    {
                        //Make sure ClientID does NOT exist in the ClientID field
                        if (reader["ClientID"].ToString().TrimEnd(trimChars).IndexOf(ClientID[0]) !=
                            -1)
                        {
                            //If we are here, then do something
                        }
                    }
                }
                reader.Close();
                reader.Dispose();
            }
        }
        // Close the connection
        if (connection != null)
        {
            connection.Close();
        }
    }
}

【问题讨论】:

  • 为黑客做好准备:SQL 注入的空间很大。或者,更好的是,使用参数化查询。
  • 这段代码在内部系统上运行,不会暴露在外部互联网上。
  • 首先,“using”是封装一次性对象的处置。然后将所有数据带到客户端,然后检查它们的 ClientID。为什么不SELECT * FROM Filelist WHERE [ToAccountName] = @ToAccountName AND ClientID = @ClientID
  • 另外:using 块清理资源,您也不需要这样做(即您的Connection.Dispose 正在复制using 块)。
  • @fraXis 大多数安全漏洞来自内部人员。使用参数化查询非常容易,而且安全。始终使用它们:默认为良好做法。此外,它们也可以更快(服务器可以缓存查询计划)。

标签: c# sql-server performance ado.net


【解决方案1】:

如果要删除第一个查询,您可以将数据库访问次数从 2 减少到 1,没有必要。

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = connection.CreateCommand())
{
    command.CommandText = "SELECT ClientID FROM dbo.Filelist WHERE ToAccountName = @param"; // note single column in select clause
    command.Parameters.AddWithValue("@param", output[0]); // note parameterized query

    connection.Open();
    using (SqlDataReader reader = command.ExecuteReader())
    {  
        while (reader.Read()) // reader.HasRow is doubtfully necessary
        {
            // logic goes here
            // but it's better to perform it on data layer too

            // or return all clients first, then perform client-side logic
            yield return reader.GetString(0);
        }
    } // note that using block calls Dispose()/Close() automatically
}

【讨论】:

  • 此代码意味着您没有在 C# 代码中执行字符串比较,并且您将在本地检索更少(可能要少得多)行。两者都不能解释神奇的数字“15”,但您修复的性能瓶颈越多,就越容易找到根本原因。
  • @ElectricLlama:我完全支持应用服务器端计算,或者更好的说法是业务逻辑,通常是这样。但是不要惊讶应用服务器将需要更多(和更多)资源。通过仔细阅读 OP,我发现这是他的问题!不是数据库服务器 CPU 使用率峰值,而是数据库请求的应用程序服务器! (请参阅他的问题本身)
【解决方案2】:

改变这个:

SELECT * FROM Filelist

到这里:

SELECT ClientID FROM Filelist

并检查性能。 我怀疑您的选择中有一个 blob 字段。 也不推荐select *,请在查询中写下您确切感兴趣的字段。

【讨论】:

  • 我怀疑你是对的,如果我是 fraXis,我肯定会看看这个。不幸的是,我们不知道“做某事”块中发生了什么:-)
【解决方案3】:

看起来没有什么明显占用 CPU 资源,但确实有一个问题很突出。

您正在运行一个查询来计算有多少条记录

"SELECT Count(*) FROM Filelist WHERE [ToAccountName] = '" + output[0] + @"'"

然后,如果返回大于 0,则您正在运行另一个查询以获取数据。

"SELECT * FROM Filelist WHERE [ToAccountName] = '" + output[0] + @"'"

这是多余的。摆脱第一个查询,只使用第二个,检查阅读器是否有数据。你也可以摆脱 HasRows 调用,直接做

using (SqlDataReader reader = command.ExecuteReader())
{
    while (reader.Read())
    {
    }
}

【讨论】:

    【解决方案4】:

    请考虑关于参数化查询的内容。

    除此之外,我认为唯一的大问题可能出现在以下块中:

    while (reader.Read())
    {
        //Make sure ClientID does NOT exist in the ClientID field
        if (reader["ClientID"].ToString().TrimEnd(trimChars).IndexOf(ClientID[0]) != -1)
        {
            //If we are here, then do something
        }
    }
    

    所以尝试将您的 reader.Read() 数据缓存在某个局部变量中,尽快释放 SQL 资源,然后您可以处理刚刚检索到的数据。例如:

    List<string> myRows = new List<string>();
    while (reader.Read())
    {
       myRows.Add(reader["ClientID"].ToString();
    }
    /// quit the using clause
    /// now elaborate what you got in myRows
    

    【讨论】:

      【解决方案5】:

      代码中没有任何内容表明存在性能问题。

      SQL Profiler 显示什么?

      (在查询计划和使用的服务器资源方面。)

      编辑:为了更清楚地说明这一点:您有一个可能表明存在问题的测量值。您现在需要更深入地测量以了解它是否真的是一个问题,只有您可以这样做(没有其他人可以访问硬件)。

      【讨论】:

      • 我是在运行 C# 代码的服务器和运行 SQL Server 2008 的服务器上运行 SQL Profiler,还是只在运行 SQL Server 2008 的服务器上运行?
      • 即使查找如此少量的记录,78 CPU 峰值是否会被视为性能问题?
      • @fraXis 78% 在具有匹配 UI 和内存的 256 核怪物服务器上将是一个问题。在 RAM 有限且结果未缓存的单个 Atom 内核上可能是正常的。
      • 即使是怪物也取决于它。如果你扫描 100TB 的数据库,78% 也是正常的。
      • 数据库很小。可能总共只有大约 300 条记录。
      【解决方案6】:

      我强烈建议您从 JetBrains 获取 dotTrace 的副本。

      至少,分析客户端代码将帮助您识别/消除 CPU 峰值的来源。

      【讨论】:

      • @Richard:这不是 OP 所说的。 OP 明确指出问题出在桌面上。
      【解决方案7】:

      我建议按照建议使用参数,但是,我看到了字符串列的类型与 C# 字符串不匹配的性能问题。在这些情况下,我建议明确指定类型。

      像这样:

      command.CommandText = "SELECT ClientID FROM dbo.Filelist WHERE ToAccountName = @accountName"; 
      command.Parameters.Add("@accountName", SqlDbType.NVarChar, 16, output[0]);
      

      或者这个:

      SqlParameter param = command.Parameters.Add(
          "@accountName", SqlDbType.NVarChar);
      param.Size = 16; //optional
      param.Value = output[0];
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-19
        • 2014-11-27
        • 2020-10-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多