1

I have a stored procedure which uses a table valued parameter (tvp). In my application, I use a datatable as a SqlParameter to match the tvp structure. The problem is that it sometimes takes 25 seconds just to insert the data (30k rows give or take) into the tvp from the application once I've already executed the stored procedure, meaning the code inside the stored procedure itself only has 5 seconds (with a command timeout of 30 seconds) to complete which doesn't always happen with large amounts of data.

I'm fully aware that I can just increase the command timeout, but I'd like to get to the bottom of why it takes 25 seconds to insert the data into the tvp and what can be done to speed this up.

Just to be clear, this isn't the code inside the stored procedure in SSMS which is taking the 25 seconds, it's the application itself inserting the rows into the tvp after I've executed the stored procedure from the application.

This offending statement is below (our tvp has roughly 20 columns):

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

My question is, why does it take 25 seconds to insert the 30k rows into the tvp and what methods can I use to speed this up? Perhaps the issue is using a DataTable for the SqlParameter? I also would have thought CommandTimeout would only start counting once the stored procedure itself has begun executing in SSMS, rather than beginning the count while preparing the parameters.

C# code below as requested (The GetDataTable method creates a DataTable by adding columns to a new DataTable which matches the definition of the tvp, and then adds rows to the DataTable by iterating over a list used elsewhere in the code).

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();
}
Conorou
  • 31
  • 4
  • 1
    Can you edit the question and share the code of stored procedure, the code which calls the stored procedure and the code which inserts 30k rows to tvp ? – Chetan Oct 23 '21 at 15:05
  • Do you mean you are getting execution timeout issue even when the stored procedure execution is completed within 5 seconds? – Chetan Oct 23 '21 at 15:07
  • Getting the timeout halfway through the stored proc. I've used Sql Profiler to measure the timings and the stored procedure only runs for 5 seconds before timing out, as the insert into the tvp has taken 25 seconds. In other instances the insert into the tvp has taken 27 seconds and the stored proc has timed out after 3 seconds. If stored proc completes then there is no timeout since total execution period did not go over the 30 seconds. Happy to add some example code from the application but it's just adding a datatable sqlparameter to a command and executing it, nothing fancy. Thanks – Conorou Oct 23 '21 at 15:16
  • Check the session wait stats after running to see if there are any waits or blocking. https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-session-wait-stats-transact-sql?view=sql-server-ver15 – David Browne - Microsoft Oct 23 '21 at 15:24
  • 2
    Please [Edit](https://stackoverflow.com/review/suggested-edits/30157880) your question to include the C# code that sets up and invokes your stored procedure. Is it possible that the code is inserting the DataTable into a table variable first before invoking the stored procedure? – AlwaysLearning Oct 23 '21 at 16:15
  • SQL Profiler itself will slow things down when using large TVPs, this is a known issue. Use Extended Events instead,. Please show C# code to pass the TVP and execute the stored procedure – Charlieface Oct 23 '21 at 18:35
  • C# code added as requested. Regarding profiler, fully aware of the performance implications, but using other 3rd database monitoring software gives me the same results I'm seeing for this issue as well, so profiler is not the cause of the problem here. Thanks – Conorou Oct 23 '21 at 21:00
  • You are not passing the parameter correctly. It should be `new SqlParameter("@tvp", SqlDbType.Structured){Value = GetDataTable(), TypeName = "YourTableType", Direction = ParameterDirection.Input});` Depending on what that software does: if it also uses a server-side trace then it's going to cause the same slowdown. Try it without any monitoring, timing from the client. – Charlieface Oct 24 '21 at 09:38
  • An update on this - noticed I was getting two different plans when calling the same proc from SSMS and from Visual Studio. Narrowed this down to the arithabort setting difference between the two. Now have turned arithabort off in SSMS (since ansi warnings is already on) and I am now seeing only one plan for the stored proc so will hopefully be able to debug in SQL further now since the plans will be the same. – Conorou Oct 25 '21 at 19:35

1 Answers1

2

I managed to grab the RPC call made by the application from profiler and use the same SQL code (and more importantly, parameters) that the application was using and run it in SSMS. In SSMS, the proc ran in about 2 seconds, from the application it was taking 30 seconds.

These are the steps that fixed this for me.

  1. Reading this great article really helped clarify the issue I was having: https://www.sommarskog.se/query-plan-mysteries.html

  2. From the article, I discovered the execution plan of the stored proc when called by the application was actually different to the execution plan when called in SSMS. I verified this by clearing the cache (DBCC FREEPROCCACHE), running the proc with the same input parameters in the app and in SSMS, and then querying sys.dm_exec_cached_plan which showed me 2 different cached plans. To fix this, I turned Arithabort ON for all applications (to match SSMS) - https://blog.sqlauthority.com/2018/08/07/sql-server-setting-arithabort-on-for-all-connecting-net-applications/

  3. As the proc in question inserts data (or deletes and reinserts to get the latest if data was outdated), I used this as a chance to help improve the processes around the data loading. This included removing a duplicate non clustered index, turning the staging table we used into a heap (this previously had a clustered index), removing the use of the TVP in the body of the proc and replacing with a temp table (as this prevents queries which use the TVP from going parallel: https://www.brentozar.com/archive/2018/06/how-table-variables-mess-with-parallelism/), using local variables to prevent parameter sniffing (i.e. declaring a new variable in the body of the proc and setting this as the value of the input param). This really helped speed up the process however, I was still getting occasional timeouts...

  4. The table which is the target of the insert/deletes from this stored proc is a very large table (100 million + rows) and also highly transactional - we insert/delete data from this table on an almost hourly basis. I noticed that this was hitting the auto update stats threshold more than once during a single load process. I also managed to match the time of the the timeouts to the time that the stats were being auto updated (https://blog.sqlauthority.com/2020/06/01/sql-server-statistics-modification-counter-sys-dm_db_stats_properties/). I turned off auto statistics on this table and instead set up a nightly job to update the statistics manually. We have not seen any further timeouts since.

Conorou
  • 31
  • 4