By: Ahmad Yaseen | Updated: 2017-03-09 | Comments (1) | Related: > Performance Tuning
Problem
How can we improve the performance of queries that use SQL Server table variables? What options are available?
Solution
When a SQL Server table variable is defined and used within your query, the related execution plan will be generated and optimized for its empty state. At runtime, the table will be filled with rows. Once that table variable is joined with other tables in the query, the initial execution plan will not be the most proper and optimal plan for its execution and it should be recompiled to generate the optimal plan for the number of rows. As the row numbers change, the query will not recompile automatically with a table variable.
To overcome this issue that may slow down the overall query performance, Trace Flag 2453, was introduced in SQL Server 2012 SP2 and later SQL versions. This trace flag can be used to allow triggering the table variable recompilation when the number of rows have changed in that table.
Assume that we need to create a table variable and fill it with data from the Production.TransactionHistory table for further processing, such as joining it with the source table to retrieve a specific data range as in the query below:
USE MSSQLTipsDemo GO DECLARE @VarTable TABLE (TransactionID INT PRIMARY KEY CLUSTERED, ProductID INT,TransactionType nchar(1)); INSERT INTO @VarTable (TransactionID, ProductID,TransactionType) SELECT TransactionID, ProductID,TransactionType FROM Production.TransactionHistory; SELECT * FROM @VarTable Vart JOIN Production.TransactionHistory TrnHist ON @VarTable.TransactionID = TrnHist.TransactionID WHERE TrnHist.TransactionDate >'2007-08-25 00:00:00.000'
The execution plan of the previous query will show us that a Clustered Index Scan will be performed on the table variable consuming 50% of the overall query execution:
The query IO statistics show us that 113,443 scans were performed on the source table with 341,903 logical reads, where only 1 scan was performed on the table variable with 268 logical read as follows:
Moreover, the query time statistics inform us that the elapsed time of 1600 ms and 578 ms of CPU time was needed:
Now, let us try the same scenario with Trace Flag 2453 enabled that will trigger the table variable recompilation when a specific number of rows are changed on that table:
USE MSSQLTipsDemo GO DECLARE @VarTable TABLE (TransactionID INT PRIMARY KEY CLUSTERED, ProductID INT,TransactionType nchar(1)); INSERT INTO @VarTable (TransactionID, ProductID,TransactionType) SELECT TransactionID, ProductID,TransactionType FROM Production.TransactionHistory; DBCC TRACEON(2453); SELECT * FROM @VarTable Vart JOIN Production.TransactionHistory TrnHist ON Vart.TransactionID = TrnHist.TransactionID WHERE TrnHist.TransactionDate >'2007-08-25 00:00:00.000' DBCC TRACEOFF(2453);
The execution plan of the previous query will show us again that a Clustered Index Scan will be performed on the table variable, but now consuming only 21% of the overall query execution:
The query IO statistics when using Trace Flag 2453 show us that only 1 scan was performed on the source table with 793 logical reads, which is negligible compared to the previous result. Only 1 scan performed on the table variable with 269 logical reads, which is similar to the previous result as follows:
Moreover, the query time statistics inform us that the query took 1495 ms of time compared with the 1600 ms in the previous case, consuming 188 ms from the CPU time, compared with the 578 ms in the previous result:
Using Trace Flag 2453 works somewhat like the OPTION (RECOMPILE) query hint with less overhead as it will trigger the recompile only when the number of changed rows exceeds a predefined threshold opposed to the OPTION (RECOMPILE) that will issue a recompile on each execution. In addition, the OPTION (RECOMPILE) will optimize the query depending on forced peek parameters, which is not the case with Trace Flag 2453.
In our simple scenario, if we try to use the OPTION (RECOMPILE) query hint to optimize the query performance instead of Trace Flag 2453:
USE MSSQLTipsDemo GO DECLARE @VarTable TABLE (TransactionID INT PRIMARY KEY CLUSTERED, ProductID INT,TransactionType nchar(1)); INSERT INTO @VarTable (TransactionID, ProductID,TransactionType) SELECT TransactionID, ProductID,TransactionType FROM Production.TransactionHistory; SELECT * FROM @VarTable Vart JOIN Production.TransactionHistory TrnHist ON Vart.TransactionID = TrnHist.TransactionID WHERE TrnHist.TransactionDate >'2007-08-25 00:00:00.000' OPTION (RECOMPILE)
The result will be identical to the Trace Flag 2453 instance and the execution plan will be the same:
The same number of scans and logical reads:
Finally, there is a small difference in the execution time and consumed CPU time as follows:
Next Steps
- As Microsoft notes, you need to use such trace flags with caution as increasing the number of query recompilations could cost more than gaining a better query performance.
- Check out Improving SQL Server performance when using table variables
- Check also Differences between SQL Server temporary tables and table variables
About the author
This author pledges the content of this article is based on professional experience and not AI generated.
View all my tips
Article Last Updated: 2017-03-09