【问题标题】:DataReader.Read running very slowDataReader.Read 运行很慢
【发布时间】:2019-09-10 09:36:45
【问题描述】:

作为问题的标题,我遇到了与从 MS-SQL 读取数据有关的问题。 我使用 DataReader 编写了一个 C# 代码,用于从查询中检索数据返回并将数据保存到 CVS。它非常简单,我很确定这段代码没有问题。

我有两个问题。第一个查询是内部连接 ​​4 个表的结果。 第二个查询使用 Row_Num 和 select 语句中的子查询。它们都返回 24 列。 第一个查询返回 321k 条记录。第二个查询返回 2300 条记录。 我使用的是 SQL Server 2008。

问题是当我使用第一个查询运行时,它运行得很快。 321k 条记录保存到 CVS 文件,需要 8 分钟才能完成。但是使用第二个查询,将 2300 条记录保存到文件需要 26 小时。我输入了登录代码,发现一些记录需要 40 - 50 分钟才能读取数据,我不知道为什么。

请看一下并给我一些建议。

下面是我的代码和我做了什么

using (var connection = new SqlConnection(connectionstring))
using (var command = new SqlCommand(secondQuery, connection))
{
    connection.Open();
    try
    {
        command.CommandTimeout = 0;
        command.CommandType = CommandType.Text;
        var reader = command.ExecuteReader();
        using (var memoryStream = ExtendedStream.CreateMemoryStream())
        using (TextWriter writer = new StreamWriter(memoryStream, Encoding.UTF8, 65536))    {
            while (reader.Read())
            {
                // using Nlog to log start time begin read data and log what happen on code
                // read property value and save to file
                // using Nlog to log end time read data
            }
        }
        reader.Dispose();
        reader.Close();
    }
    catch (SqlException ex)
    {
        throw ex;
    }
    finally
    {
        command.Dispose();
        connection.Dispose();
        connection.Close();
    }
}

第二次查询

WITH TEMP AS
(
    SELECT ROW_NUMBER() OVER (PARTITION BY SId,PointID ORDER BY UTCSysTime) Record_No, * FROM
    (SELECT
        *
        FROM
        Table_A WITH (NOLOCK)
        WHERE
        StatusID IN (1, 2)
        AND [UTCSysTime] >= @StartTime
        AND [UTCSysTime] <= @EndTime
        UNION
        SELECT
        E.*
        FROM
        Table_A E WITH (NOLOCK)
        INNER JOIN (SELECT
                        SId,
                        PointID,
                        StatusID,
                        UTCSysTime = MIN(UTCSysTime)
                    FROM
                        Table_A WITH (NOLOCK)
                    WHERE
                        StatusID IN (1, 2)
                        AND [UTCSysTime] > @EndTime
                    GROUP BY
                        SId,
                        PointID,
                        StatusID
                    ) E2
            ON E.SId = E2.SId
                AND E.PointID = E2.PointID
                AND E.StatusID = E2.StatusID
                AND E.UTCSysTime = E2.UTCSysTime
    ) T
)

SELECT
    *,
    [PointShortName] = CONVERT(VARCHAR(255), (SELECT TOP 1
                                            ShortName
                                          FROM
                                            Table_B _T
                                            WITH (NOLOCK)
                                          WHERE
                                            _T.PointID = Table_T.PointID
                                         )),
    [Description_Point] = CONVERT(VARCHAR(2000), (SELECT TOP 1
                                            _T.Name
                                           FROM
                                            Table_Point _T
                                            WITH (NOLOCK)
                                           WHERE
                                            _T.PointID = Table_T.PointID
                                            AND _T.DescID = 1
                                          )),
    [Description_1] = CONVERT(VARCHAR(255), (SELECT TOP 1
                                                            Name
                                                         FROM
                                                            Table_C
                                                            WITH (NOLOCK)
                                                         WHERE
                                                            DescID = 1
                                                            AND CatID = [Table_T].[Cat1]
                                                        )),
    [Description_2] = CONVERT(VARCHAR(255), (SELECT TOP 1
                                                            Name
                                                         FROM
                                                            Table_C
                                                            WITH (NOLOCK)
                                                         WHERE
                                                            DescID = 1
                                                            AND CatID = [Table_T].[Cat2]
                                                        )),
    [Description_3] = CONVERT(VARCHAR(255), (SELECT TOP 1
                                                            Name
                                                         FROM
                                                            Table_C
                                                            WITH (NOLOCK)
                                                         WHERE
                                                            DescID = 1
                                                            AND CatID = [Table_T].[Cat3]
                                                        )),
    [Description_4] = CONVERT(VARCHAR(255), (SELECT TOP 1
                                                            Name
                                                         FROM
                                                            Table_C
                                                            WITH (NOLOCK)
                                                         WHERE
                                                            DescID = 1
                                                            AND CatID = [Table_T].[Cat4]
                                                        ))
FROM
    (SELECT
        *
     FROM
        (SELECT 
            a.*
            ,UTCSysTime_End = ISNULL(CASE WHEN b.StatusID = 1
                                                 THEN b.UTCSysTime
                                            END,
                                            CASE WHEN c.StatusID = 1
                                                 THEN c.UTCSysTime
                                            END)

  FROM TEMP a LEFT OUTER JOIN  TEMP b ON a.Record_no = b.Record_no - 1 and a.SId = b.SId and a.PointID = b.PointID
  LEFT OUTER JOIN  TEMP c ON a.Record_no = c.Record_no - 2 and a.SId = c.SId and a.PointID = c.PointID) E
     WHERE
        E.StatusID = 2
        AND [UTCSysTime] >= @StartTime
        AND [UTCSysTime] <= @EndTime
    ) [Table_T]
WHERE
    [UTCSysTime] > @StartTime
    AND [UTCSysTime] <= @EndTime

我尝试在 SSMS 上运行查询,它们都运行良好。我尝试使用 SQL Profiler,但我的帐户没有权限。所以我把日志放到源代码中。我看到一些记录需要 50-60 分钟才能读取和写入文件。

日志是:

2019-09-09 07:48:40.5242 |调试 |结束写入行号 226 |例外:
2019-09-09 07:48:40.5522 |调试 |开始写第 227 行 |例外:
2019-09-09 07:48:40.5802 |调试 |结束写入行号 227 |例外:
2019-09-09 07:48:40.6072 |调试 |开始写第 228 行 |例外:
2019-09-09 07:48:40.6352 |调试 |结束写入行号 228 |例外:
2019-09-09 07:48:40.6632 |调试 |开始写第 229 行 |例外:
2019-09-09 08:20:50.3977 |调试 |结束写入行号 229 |例外:
2019-09-09 08:20:50.4337 |调试 |开始写第 230 行 |例外:
2019-09-09 08:20:50.4667 |调试 |结束写入行号 230 |例外:
2019-09-09 08:20:50.5047 |调试 |开始写第 231 行 |例外:
2019-09-09 08:20:50.5387 |调试 |结束写入行号 231 |例外:

【问题讨论】:

  • 不知道您的选择语句,我只能建议检查 SQL Profiler 和 SSMS 执行计划
  • 你有子选择和row_num?看起来该查询可以使用一些索引/统计信息(对于 row_num)并且应该消除子查询。
  • "...and Select in select method" 在内部,第二个查询中的 SQL Server 正在执行:1 选择主数据块,对 row_num 进行表扫描,以及 2300 子查询的额外选择语句(“选择中的选择”)。如果您在第二条 SQL 语句方面需要帮助,请编辑您的问题并添加它。检查minimal reproducible example
  • 尝试使用reader.GetOrdinal("CloumnName") 对抗reader[i] 可能会对性能产生巨大影响。
  • it take 26 hours to save 2300 records to files 生成的文件有多大?

标签: c# sql datareader sqlperformance


【解决方案1】:

这里首先要做的是识别问题是输入还是输出;为此,我会简单地注释掉所有输出代码,所以你有(只显示未注释的代码):

while (reader.Read())
{
    count++;
} 
Console.WriteLine(count);

如果这仍然很慢,那么问题出在数据库上。我们看不到您的查询、表、索引或数据 - 因此我们无法帮助您调整它,但是:您需要查看的地方。

如果没有输出代码很快,问题就出在你的代码上。你显示ExtendedStream.CreateMemoryStream;这似乎不是常规框架的一部分,因此我无法评论预期的执行方式,但是如果您正在编写文件,坦率地说,我会尝试仅使用内置框架类型为此 - FileStream

我还将最小化Console 输出的数量,可能只显示一些 N 的每 N 行更新。您不是在尝试分析控制台。

【讨论】:

  • 由于延迟,我将赌注押在数据库上。但遗憾的是,没有任何关于执行的 SQL 查询的信息。
  • @bradbury9 确实;我在这一点上加倍强调:“我们看不到您的查询、表、索引或数据 - 所以我们无法帮助您调整它,但是:这是您需要查看的地方。”
  • 如果问题来自 while {} 中的代码块,第一次查询应该也很慢吧?
  • @CaPhamVan 很复杂:“这取决于”。这是TTFB和TTLB之间的区别。如果您的查询需要很长时间才能开始,那么是的,它可能会很慢甚至进入循环之前。
  • 如果查询在 select 语句中有子查询,循环可能需要很长时间。这是一个 O(n) 问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-12
相关资源
最近更新 更多