ADO.NET 调用 T-SQL 存储过程会导致 SqlTimeoutException

Posted

技术标签:

【中文标题】ADO.NET 调用 T-SQL 存储过程会导致 SqlTimeoutException【英文标题】:ADO.NET calling T-SQL Stored Procedure causes a SqlTimeoutException 【发布时间】:2010-10-24 11:07:20 【问题描述】:

我有一个带有签名的 T-SQL 存储过程

CREATE PROCEDURE MyProc
@recordCount INT OUTPUT
@param1 INT
...

当直接在 Sql Server 中执行时,该过程在 5 秒内运行,返回几个结果集,总计约 100 行。

使用 ADO.NET SqlDataAdapter.Fill 方法调用此过程以填充 Dataset 会在 3 分钟(指定的超时间隔)后导致 SqlCommand 上的 SqlTimeoutException

更改存储过程,使其不再有输出参数,并将所需的输出值作为最后一个结果集返回,解决了问题,整个过程按预期在 5 秒内运行。

但是为什么呢?

我不想在不了解我是否真的解决了问题的情况下查看我的代码库并修改所有此类行为的实例。

另外需要注意的是,这仅在一个特定的服务器上很明显,该服务器拥有比我们运行的其他类似数据库更大的数据集。确定不是 Sql Server 设置?

更新

进入框架源,问题似乎在于元数据检索。 SqlDataReader 对象的 ConsumeMetaData 方法无限期挂起。但是我在其他数据库上运行了测试并且无法重现,因此当通过 ADO.NET 调用此过程时,这是一个特定于数据库的问题......太好了。

更新 II

已确认如果我将代码更改为将OleDbDataAdapter 与 SQLOLEDB 或 SQLNCLI 提供程序类型一起使用,问题仍然存在。肯定与连接有关。

【问题讨论】:

如果您使用数据阅读器,这是否有效?您是否查看过 sql profiler 而它卡在 sqldataadapter.fill 中? 同样处理 SqlDataReader。也对其进行了分析。交互式地我看到它需要不到 5 秒的持续时间,通过 ADO.NET 的完全相同的调用,探查器显示它需要超时持续时间(180000 毫秒)。 Fill 方法挂起并保持连接直到超时。 【参考方案1】:

一旦我确定问题的根源是 ADO.NET 连接,thread 引导我找到了答案。

默认情况下,通过 Sql Server Management Studio (SSMS) 的连接基本上具有 SET ARITHABORT ON。 ADO.NET 连接没有。

设置ARITHABORT OFF 并直接通过 SSMS 执行查询给了我同样缓慢的响应时间。

使用或不使用此设置运行时的主要区别是为两个调用创建不同的查询计划。当ARITHABORTOFF 时,SSMS 命令将使用 ADO.NET 连接正在使用的预编译缓存查询计划,因此会超时。

无论ARITHABORT 设置如何,通过以管理员身份在数据库上运行以下命令,所有查询都会按预期运行。

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

我只能假设已编译的查询计划已损坏或无效。

我将在另一个thread 上将此作为解决方案(我已投票赞成)

谢谢。

【讨论】:

警告这个答案只是一个非常短期的修复,完全不必要的残酷。 DBCC DROPCLEANBUFFERS 将从缓存中删除大部分数据页并且无效DBCC FREEPROCCACHE 将刷新整个程序缓存,只是为了删除一个有问题的计划!无法保证该问题不会在未来某个阶段再次发生。问题是参数嗅探。请参阅this article 以获得更全面的解释。 @MartinSmith:我已经阅读了那篇文章,并且很难真正理解它。您能否解释一下人们在不运行这两行代码的情况下可以做些什么来解决这个问题,以及人们可以做些什么来避免将来发生这种情况? @StriplingWarrior 另一个好文章在这里讨论了各种可能的预防措施sqlperformance.com/2013/08/t-sql-queries/… 对存储过程使用 WITH RECOMPILE;。阅读:sqladvice.com/blogs/gstark/archive/2008/02/12/… WITH RECOMPILE 对于存储过程来说不是一个好的选择。每次调用它都会增加开销。最好解决根本问题。【参考方案2】:

我的立场是正确的 - 是的,您 可以 两者都有 - 一个 OUTPUT 参数以及一组被返回的行。你每天都会学到新东西:-)

至于为什么会发生超时 - 嗯....很难说。一个快速的小样本对我来说很好。你能发布你的存储过程(至少是它的相关部分)吗?

我们讨论了多少行,会在这里返回?

您在存储过程中的什么时候计算需要作为 OUTPUT 参数返回的行数?

如果您尝试将另一个参数 MaxRows 添加到您的一个 SProc 作为测试并在您的数据上执行 SELECT TOP (@MaxRows)....... 怎么办?会很快恢复吗?

马克

【讨论】:

你确定吗,我可以发誓我之前使用输出参数和结果集 正如我所说,我交互式运行没有问题。 @marc_s 如问题所述,大约返回 100 行(即最少)。正如问题所述,这一切都在几秒钟内以交互方式工作。只有通过 ADO.NET 调用时,我们才会遇到问题。该过程运行良好,除非您通过 ADO.NET 调用它来填充数据集。我正在逐步浏览 SqlDataAdapter.Fill 的源代码,以查看它挂起的位置/原因。谢谢。【参考方案3】:

简而言之 - 我通过强制 SQL Server 在无法自行解决问题时使用最合适的索引来限制 lob 逻辑读取来解决了我的问题。

长篇——

在尝试了所有其他建议的答案后,我刚刚遇到了这个问题并以不同的方式解决了它。在 SSMS 中,查询在大约 3 秒内运行,但在从 .Net MVC Web 应用程序调用时超时。

SSMS 中的统计 IO 输出告诉我,一个表上有超过 195,500,000 次 lob 逻辑读取(具有聚集列存储索引的 20M 行表,也具有行索引,但没有“LOB”列)。我从执行计划中注意到,大部分负载 (76%) 来自对其中一个行索引的索引查找。我使用了以下内容:

from [table] with (index([clustered columnstore index name]))

在我的查询中强制使用聚集列存储索引,我的查询减少到 195M 下降到

我尝试了选项重新编译,设置 arithabort,参数嗅探,最后 SQL Server 无法确定要使用哪个索引。顺便说一句,这也是一个极端情况,也是我唯一一次不得不在这个数据库中强制索引。

【讨论】:

以上是关于ADO.NET 调用 T-SQL 存储过程会导致 SqlTimeoutException的主要内容,如果未能解决你的问题,请参考以下文章

通过 ADO.Net 命令调用 Informix 存储过程的最佳/正确方法?

这样的存储过程会导致死锁吗?

使用 ADO.NET 获取 Oracle 包中过程的存储过程元数据

ADO.net 是仅在指定的存储过程调用之后返回结果,还是等到所有触发器都执行后才返回结果?

在 ASP.net 中运行时调用存储过程

如何使用通用存储库实现 ADO.NET?