Why is a stored procedure slower than bare T-SQL?

I have a stored procedure in an MS-SQL 2005 database that:

  • Creates two temporary tables
  • Fulfills a query with 7 joins, but otherwise is not terribly complicated
  • Inserts results in one of the temporary tables
  • It performs two more queries (no connections to "real" tables), which put records from one of the temporary tables to another.
  • Returns a result set from the second temp table
  • Reduces both temporary tables

SP takes two parameters, which are then used in the first request.

When I run SP for a given set of parameters, it takes 3 minutes to complete.

When I run the contents of SP as a regular batch of T-SQL (declaring and setting parameters in advance), it takes 10 seconds. These numbers are consistent between several consecutive runs.

This is a huge difference, and there are no obvious functional changes. What could be the reason for this?

UPDATE

Re-indexing my tables (DBCC REINDEX) dramatically accelerated the SP version. The SP version now takes 1 second, while raw SQL takes 6.

This is great as a solution to an immediate problem, but I would still like to know why.

+4
source share
4 answers

Perhaps this happened precisely because the execution plan was cached in SP and was not optimal for the data set. When the data set is highly dependent on parameters or changes significantly between calls, it is better to specify "recompile" in "create proc". You lose a fraction of a second when recompiling, but you can win minutes when executed.

PS Why can not I comment? Only "Your answer" is available.

+11
source

I experienced the exact same problem several times recently (with MS-SQL 2008). Specific stored procedures will be extremely slow to start (minutes), but the same SQL inserted into SSMS took only a few seconds.

The problem is that the stored procedure uses a poor execution plan, while the inserted SQL uses a different (and much better) execution plan.

Compare execution plans

To test this hypothesis, open a new query window in SSMS and turn on β€œEnable actual execution plan” ( Ctrl-M is the keyboard shortcut for this).

Then paste the contents of the stored procedure into the window and follow this when calling the actual stored procedure. For instance:

SELECT FirstName, LastName FROM Users, where ID = 10

EXEC dbo.spGetUserById 10

Run both queries together, and then compare the execution plans for both. I have to say that in my case, the "Request Cost" rating for each request did not help at all and pointed me in the wrong direction. Instead, look carefully at the indexes used, whether scans are performed instead of searching, and how many rows are processed.

There should be a difference in plans, and this should help identify tables and indexes that need to be explored further.

To fix this problem, in one instance, I was able to rewrite the stored procedure to avoid using index scanning and instead rely on index queries. In another instance, I found that the update, which rebuilds the indexes for the specific table used in the query, did everything possible.

Search and update indexes

I used this SQL to find and restore the corresponding indexes:

 /* Originally created by Microsoft */ /* Error corrected by Pinal Dave (http://www.SQLAuthority.com) */ /* http://blog.sqlauthority.com/2008/03/04/sql-server-2005-a-simple-way-to-defragment-all-indexes-in-a-database-that-is-fragmented-above-a-declared-threshold/ */ /* Catch22: Added parameters to filter by table & view proposed changes */ -- Specify your Database Name USE AdventureWorks /* Parameters */ Declare @MatchingTableName nvarchar(100) = 'MyTablePrefix' -- Specify Table name (can be prefix of table name) or blank for all tables DECLARE @ViewOnly bit = 1 -- Set to 1 to view proposed actions, set to 0 to Execute proposed actions: -- Declare variables SET NOCOUNT ON DECLARE @tablename VARCHAR(128) DECLARE @execstr VARCHAR(255) DECLARE @objectid INT DECLARE @indexid INT DECLARE @frag decimal DECLARE @maxreorg decimal DECLARE @maxrebuild decimal DECLARE @IdxName varchar(128) DECLARE @ReorgOptions varchar(255) DECLARE @RebuildOptions varchar(255) -- Decide on the maximum fragmentation to allow for a reorganize. -- AVAILABLE OPTIONS: http://technet.microsoft.com/en-us/library/ms188388(SQL.90).aspx SET @maxreorg = 20.0 SET @ReorgOptions = 'LOB_COMPACTION=ON' -- Decide on the maximum fragmentation to allow for a rebuild. SET @maxrebuild = 30.0 -- NOTE: only specifiy FILLFACTOR=x if x is something other than zero: SET @RebuildOptions = 'PAD_INDEX=OFF, SORT_IN_TEMPDB=ON, STATISTICS_NORECOMPUTE=OFF, ALLOW_ROW_LOCKS=ON, ALLOW_PAGE_LOCKS=ON' -- Declare a cursor. DECLARE tables CURSOR FOR SELECT CAST(TABLE_SCHEMA AS VARCHAR(100)) +'.'+CAST(TABLE_NAME AS VARCHAR(100)) AS Table_Name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME like @MatchingTableName + '%' -- Create the temporary table. if exists (select name from tempdb.dbo.sysobjects where name like '#fraglist%') drop table #fraglist CREATE TABLE #fraglist ( ObjectName CHAR(255), ObjectId INT, IndexName CHAR(255), IndexId INT, Lvl INT, CountPages INT, CountRows INT, MinRecSize INT, MaxRecSize INT, AvgRecSize INT, ForRecCount INT, Extents INT, ExtentSwitches INT, AvgFreeBytes INT, AvgPageDensity INT, ScanDensity decimal, BestCount INT, ActualCount INT, LogicalFrag decimal, ExtentFrag decimal) -- Open the cursor. OPEN tables -- Loop through all the tables in the database. FETCH NEXT FROM tables INTO @tablename WHILE @@FETCH_STATUS = 0 BEGIN -- Do the showcontig of all indexes of the table INSERT INTO #fraglist EXEC ('DBCC SHOWCONTIG (''' + @tablename + ''') WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS') FETCH NEXT FROM tables INTO @tablename END -- Close and deallocate the cursor. CLOSE tables DEALLOCATE tables -- Declare the cursor for the list of indexes to be defragged. DECLARE indexes CURSOR FOR SELECT ObjectName, ObjectId, IndexId, LogicalFrag, IndexName FROM #fraglist WHERE ((LogicalFrag >= @maxreorg) OR (LogicalFrag >= @maxrebuild)) AND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0 -- Open the cursor. OPEN indexes -- Loop through the indexes. FETCH NEXT FROM indexes INTO @tablename, @objectid, @indexid, @frag, @IdxName WHILE @@FETCH_STATUS = 0 BEGIN IF (@frag >= @maxrebuild) BEGIN IF (@ViewOnly=1) BEGIN PRINT 'WOULD be executing ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REBUILD WITH ( ' + @RebuildOptions + ' ) -- Fragmentation currently ' + RTRIM(CONVERT(VARCHAR(15),@frag)) + '%' END ELSE BEGIN PRINT 'Now executing ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REBUILD WITH ( ' + @RebuildOptions + ' ) -- Fragmentation currently ' + RTRIM(CONVERT(VARCHAR(15),@frag)) + '%' SELECT @execstr = 'ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REBUILD WITH ( ' + @RebuildOptions + ' )' EXEC (@execstr) END END ELSE IF (@frag >= @maxreorg) BEGIN IF (@ViewOnly=1) BEGIN PRINT 'WOULD be executing ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REORGANIZE WITH ( ' + @ReorgOptions + ' ) -- Fragmentation currently ' + RTRIM(CONVERT(VARCHAR(15),@frag)) + '%' END ELSE BEGIN PRINT 'Now executing ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REORGANIZE WITH ( ' + @ReorgOptions + ' ) -- Fragmentation currently ' + RTRIM(CONVERT(VARCHAR(15),@frag)) + '%' SELECT @execstr = 'ALTER INDEX ' + RTRIM(@IdxName) + ' ON ' + RTRIM(@tablename) + ' REORGANIZE WITH ( ' + @ReorgOptions + ' )' EXEC (@execstr) END END FETCH NEXT FROM indexes INTO @tablename, @objectid, @indexid, @frag, @IdxName END -- Close and deallocate the cursor. CLOSE indexes DEALLOCATE indexes -- Delete the temporary table. DROP TABLE #fraglist 
+2
source

Does your SP use dynamic T-SQL at all? If so, you are losing the benefits of cached execution plans ...

Otherwise, are the connections used to run SP vs T-SQL in the same way? Is the speed differential consistent or is the SP slow the first time it starts after leveling?

0
source

this problem is solved using different approaches as shown by Greg Larsen Visit https://www.simple-talk.com/sql/t-sql-programming/parameter-sniffing/

0
source

All Articles