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

Execution timeout expiry when executing a stored procedure which uses a table valued parameter

我有一个使用 table 值参数 (tvp) 的存储过程。在我的应用程序中,我使用 datatable 作为 SqlParameter 来匹配 tvp 结构。问题是,一旦我已经执行了存储过程,有时仅需要 25 秒就可以将数据(30k 行给予或接受)从应用程序插入 tvp,这意味着存储过程本身中的代码只有 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();
}

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

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

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

  2. 从这篇文章中,我发现存储过程在应用程序调用时的执行计划实际上与在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/

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

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