【问题标题】:Fast in SSMS and slow in the application - Why does it take this DataSet so long to fill?SSMS 快而应用程序慢 - 为什么这个 DataSet 需要这么长时间才能填充?
【发布时间】:2012-02-28 22:40:57
【问题描述】:

我有一个从查询中填充的数据集,如下所示...

SELECT  DISTINCT ColA, ColB, ColC, ColD, ColE, ColF, dbo.CustomFunction(ColA) AS ColG
FROM    TableA
    JOIN ViewA ON ColA = ViewColA 
WHERE   ColB = @P1 AND ColC = @P2 AND ColD = @P3 AND ColE = @P4 
ORDER BY ColB, ColC DESC, ColA

(查询字段等被混淆)

我已经分析了这个查询,它在 SSMS 中运行 12 秒后返回了大约 200 行。请注意,我重新启动了服务器并使用了所需的 DBCC 命令来确保没有使用现有的执行计划。

但是,当我从 .Net 应用程序运行此查询时,填充数据集需要 30 多秒,并且默认 ADO.Net 命令超时 30 秒。

如果查询在 12 秒内运行,我无法理解为什么将 200 行填充到数据集中需要超过 18 秒。除非这里发生了我不知道的事情。我想 ADO.Net 只是调用查询,获取数据并填充它。

人口代码看起来像这样(注意我从另一个开发人员那里继承了这个)...

DataSet res = new DataSet();

    try
    {
        using (SqlDataAdapter da = new SqlClient.SqlDataAdapter())
        {
            var cmd = new SqlClient.SqlCommand();
            String params = FillParameters(cmd, _params, params);
            cmd.CommandText = params + SQL;
            cmd.Connection = conn;
            cmd.Transaction = _transaction;

            if (CommandTimeout.HasValue)
            {
                cmd.CommandTimeout = CommandTimeout.Value;
            }

            da.SelectCommand = cmd;
            da.Fill(res);
            return res;
        }
    }
    catch
    {
        throw;
    }

在调试中运行它,当填充方法被命中时,该方法大约需要 50 秒才能完成。这可以通过在 ADO.Net 命令上设置高超时来证明。我对查询的性能感到满意,我可以在大约 12 秒内始终如一地运行,那么为什么要额外花费 18 多秒来填充数据集?

ADO.Net 是否在执行此代码的某些操作(可能是由于结构原因),这意味着填充数据集需要超过 18 秒?我尝试将 EnforceConstraints 设置为 false,这没有任何区别。

需要注意的一点是,由于该程序的设计,将超过所需数量的参数输入到 sql 命令中。 FillParameters 方法执行此操作。有 20 个左右的“默认”参数添加到命令中,但只有例如此查询使用了 4 个。

总之,

  • 会发生什么导致填充 DS 需要 18 秒以上的时间?

  • ADO.Net 是否对我的数据集做了一些“聪明”的事情,而不仅仅是运行查询和填充数据集?

  • 可能是传入的参数过多导致了问题。

谢谢。

【问题讨论】:

  • 这一行发生了什么 -> cmd.Transaction = _transaction; ADO.NET 命令支持事务,但事务在服务器上执行效率最高,即在 proc 中使用 BEGIN TRANSACTION。
  • @kd7 - 在这种情况下 _transaction 为空,它不应该影响任何东西。这是我在很多地方看到的通用程序。我认为当它用于更新时,可以将事务传递给它。
  • 您是否使用相同的连接设置运行?特别是来自 .Net 的调用中的事务隔离级别是什么?事务隔离级别等连接设置会影响查询的时间。另外,我认为 12 秒是一个很长的时间,除非你有几千万或几亿行。请考虑更好的索引。
  • @Ben - 查询很好,因为有很多数据要处理。我不明白的是,运行查询只需要 12 秒,而 ADO.Net 又需要 28 多秒才能将该数据填充到数据集中?
  • 从 `select transaction_isolation_level,* from sys.dm_exec_sessions' 中找出进程的事务隔离级别 Session_id 是会话的 SPID。 host_name 和 program_name 将帮助从列表中识别正确的会话。

标签: .net sql ado.net dataset


【解决方案1】:

尝试在您的查询中发出 SET ARITHABORT ON。它确实解决了我的问题

【讨论】:

  • 为什么要删除你的答案,然后再次发布完全相同的答案?
【解决方案2】:

问题在于现有代码强制执行可序列化隔离级别。

我使用 SQL Server Profiler 比较了来自通过 SSMS 运行的查询和应用程序的命令和执行统计信息。

--- SSMS ---
....
....
set transaction isolation level read committed

CPU: 7797
Reads: 338,425
Writes: 1685
Duration: 7,912

--- Application ---
....
....
set transaction isolation level serializable 

CPU: 46,531
Reads: 241,202
Writes: 0
Duration: 46,792

然后我在 SSMS 中同时使用 Set transaction isolution level serializableexec sp_executesql 运行查询,这样 SQL Server 就没有来自 SSMS 的关于查询包含什么的提示。

这在 SSMS 和应用程序中重现了 30 多秒的执行时间。

这只是修改代码以使用Read Committed 隔离级别的情况。

参考资料: http://www.sommarskog.se/query-plan-mysteries.html#otherreasons

【讨论】:

    【解决方案3】:

    我认为您的问题不在代码中,而在数据库中,如果您的数据库很大,那么这可能是您的问题,尝试使用以下方法更新数据库中的统计信息:

    EXEC sp_updatestats

    【讨论】:

    • 不,不是这样 - 查询在 12 秒内运行。剩下的时间是 ADO.Net。
    【解决方案4】:

    假设你的表有主键

    1) 检查现有索引碎片 如果碎片超过 30%,则重建索引,否则重新组织 2) 检查缺失的索引列 根据缺失列创建非集群索引

    然后重新运行您的 sql 脚本。通常在管理适当的索引后应该会有所改善。

    【讨论】:

    • 查询正常。它的 ADO.Net 出于某种原因需要更多时间。
    猜你喜欢
    • 2012-04-29
    • 2020-08-28
    • 2022-08-22
    • 2017-09-22
    • 2020-02-15
    • 1970-01-01
    • 2012-05-11
    • 2019-11-13
    • 1970-01-01
    相关资源
    最近更新 更多