执行使用表值参数的存储过程时执行超时到期

Posted

技术标签:

【中文标题】执行使用表值参数的存储过程时执行超时到期【英文标题】:Execution timeout expiry when executing a stored procedure which uses a table valued parameter 【发布时间】:2021-12-09 19:58:24 【问题描述】:

我有一个使用表值参数 (tvp) 的存储过程。在我的应用程序中,我使用数据表作为 SqlParameter 来匹配 tvp 结构。问题是,一旦我已经执行了存储过程,有时将数据(30k 行给或取)从应用程序插入 tvp 需要 25 秒,这意味着存储过程本身的代码只有 5 秒(命令超时 30 秒)来完成,这并不总是发生在大量数据中。

我完全知道我可以增加命令超时时间,但我想深入了解为什么需要 25 秒才能将数据插入 tvp 以及可以采取哪些措施来加快速度。

需要明确的是,这不是 SSMS 中存储过程中的代码,它需要 25 秒,而是应用程序本身在我从应用程序执行存储过程后将行插入到 tvp。

这个有问题的陈述如下(我们的 tvp 大约有 20 列):

declare @p3 dbo.table_valued_parameter insert into @p3 (col1, col2, col3) values (v1, v2, v3)

我的问题是,为什么将 30k 行插入 tvp 需要 25 秒,我可以使用哪些方法来加快速度?也许问题是为 SqlParameter 使用 DataTable?我还以为 CommandTimeout 只会在存储过程本身开始在 SSMS 中执行后才开始计数,而不是在准备参数时开始计数。

下面的 C# 代码按要求(GetDataTable 方法通过将列添加到与 tvp 的定义匹配的新 DataTable 来创建 DataTable,然后通过迭代代码中其他地方使用的列表将行添加到 DataTable)。

List<SqlParameter> parameters = new List<SqlParameter>()

    new SqlParameter("@textParam1", "Value1"), 
    new SqlParameter("@testParam2", "Value2"),
    new SqlParameter("@tvp", GetDataTable())
;

DataSet dataSet = new DataSet();

SqlCommand command = new SqlCommand(StoredProcName); 

command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(parameters.ToArray());

using (SqlConnection connection = new SqlConnection(ConnectionString))

    connection.Open();
    command.Connection = connection;

    using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
    
        dataAdapter.Fill(dataSet);
    

    connection.Close();

【问题讨论】:

你能编辑问题并分享存储过程的代码,调用存储过程的代码和向tvp插入30k行的代码吗? 您的意思是即使存储过程执行在 5 秒内完成,您也会遇到执行超时问题? 在存储过程中途获得超时。我使用 Sql Profiler 来测量时间,存储过程在超时之前只运行 5 秒,因为插入到 tvp 需要 25 秒。在其他情况下,插入 tvp 需要 27 秒,并且存储的 proc 在 3 秒后超时。如果存储过程完成,则没有超时,因为总执行时间没有超过 30 秒。很高兴从应用程序中添加一些示例代码,但它只是将数据表 sql 参数添加到命令并执行它,没什么特别的。谢谢 运行后检查会话等待统计信息,看看是否有任何等待或阻塞。 docs.microsoft.com/en-us/sql/relational-databases/… 请Edit您的问题包括设置和调用您的存储过程的C#代码。代码是否有可能在调用存储过程之前先将 DataTable 插入到表变量中? 【参考方案1】:

我设法从分析器中获取应用程序发出的 RPC 调用,并使用应用程序正在使用的相同 SQL 代码(更重要的是,参数)并在 SSMS 中运行它。在 SSMS 中,proc 在大约 2 秒内运行,从应用程序开始需要 30 秒。

这些是为我解决此问题的步骤。

    阅读这篇精彩的文章确实有助于澄清我遇到的问题:https://www.sommarskog.se/query-plan-mysteries.html

    从文章中,我发现应用程序调用时存储过程的执行计划实际上与在 SSMS 中调用时的执行计划不同。我通过清除缓存(DBCC FREEPROCCACHE)验证了这一点,在应用程序和 SSMS 中使用相同的输入参数运行 proc,然后查询 sys.dm_exec_cached_plan,它显示了 2 个不同的缓存计划。为了解决这个问题,我为所有应用程序打开了 Arithabort(以匹配 SSMS)-https://blog.sqlauthority.com/2018/08/07/sql-server-setting-arithabort-on-for-all-connecting-net-applications/

    由于有问题的 proc 插入数据(或删除并重新插入以获取最新数据,如果数据已过时),我借此机会帮助改进围绕数据加载的流程。这包括删除重复的非聚集索引,将我们使用的暂存表转换为堆(这之前有一个聚集索引),删除 proc 主体中 TVP 的使用并替换为临时表(因为这样可以防止查询它使用 TVP 并行:https://www.brentozar.com/archive/2018/06/how-table-variables-mess-with-parallelism/),使用局部变量来防止参数嗅探(即在 proc 的主体中声明一个新变量并将其设置为输入参数的值)。这确实有助于加快进程,但是我仍然偶尔会超时......

    从这个存储过程中插入/删除的目标表是一个非常大的表(1 亿多行)并且也是高度事务性的 - 我们几乎每小时从这个表中插入/删除数据。我注意到这在单个加载过程中不止一次达到自动更新统计阈值。我还设法将超时时间与统计信息自动更新的时间相匹配(https://blog.sqlauthority.com/2020/06/01/sql-server-statistics-modification-counter-sys-dm_db_stats_properties/)。我关闭了该表的自动统计信息,而是设置了一个夜间作业来手动更新统计信息。从那以后,我们没有看到任何进一步的超时。

【讨论】:

以上是关于执行使用表值参数的存储过程时执行超时到期的主要内容,如果未能解决你的问题,请参考以下文章

表值函数真的很奇怪的问题

在使用表值参数时如何将多个参数一起传递给存储过程

使用 PetaPoco 将表值参数传递给存储过程

表值参数

从表值参数c#中检索数据

SQL Server存储过程中使用表值作为输入参数示例