为什么存储过程比裸T-SQL运行得慢?

时间:2010-08-27 00:22:15

标签: sql-server sql-server-2005 performance stored-procedures

我在MS-SQL 2005数据库中有一个存储过程:

  • 创建两个临时表
  • 使用7个连接执行查询,但不是非常复杂
  • 将结果插入其中一个临时表
  • 执行另外两个查询(没有连接到“真实”表),将一个临时表中的记录放入另一个临时表中。
  • 从第二个临时表
  • 返回结果集
  • 删除两个临时表

SP采用两个参数,然后在第一个查询中使用。

当我为给定的一组参数运行SP时,执行需要3分钟。

当我执行SP的内容作为常规T-SQL批处理(事先声明并设置参数)时,需要10秒钟。这些数字在多个连续运行中是一致的。

这是一个巨大的差异,并没有明显的功能变化。可能导致这种情况的原因是什么?

更新

重新索引我的表格(DBCC REINDEX)大大加快了SP版本 。 SP版本现在需要1秒,而原始SQL需要6秒。

这对解决当前问题很有帮助,但我仍然想知道“为什么”。

4 个答案:

答案 0 :(得分:11)

这可能正是由于在SP中执行计划被缓存并且对于数据集而言不是最佳的。当数据集在很大程度上取决于调用之间的参数或变化时,最好在'create proc'中指定'with recompile'。你在重新编译时会失去一小部分时间,但可能会在执行时赢得几分钟。

PS为什么我不能发表评论?只有“你的答案”可用。

答案 1 :(得分:2)

我最近经历了几次相同的问题(使用MS-SQL 2008)。特定的存储过程运行速度极慢(分钟),但粘贴到SSMS中的相同SQL只花了几秒钟。

问题基本上是存储过程使用错误的执行计划,而粘贴的SQL使用不同的(并且更好)执行计划。

比较执行计划

要测试此假设,请在SSMS中打开一个新的查询窗口,然后启用“包括实际执行计划”(Ctrl-M是此键盘快捷键。)

然后将存储过程的内容粘贴到窗口中,然后调用实际的存储过程。 例如:

  

SELECT FirstName,LastName FROM Users,其中ID = 10

     

EXEC dbo.spGetUserById 10

同时运行两个查询,然后比较两者的执行计划。我不得不说,在我的情况下,每个查询的“查询成本”估计根本没有帮助,并指出我的方向错误。相反,请仔细查看正在使用的索引,是否正在执行扫描而不是搜索以及正在处理的行数。

计划应该有所不同,这应该有助于确定表格和表格。需要进一步调查的指数。

为了帮助解决问题,在一个实例中,我能够重写存储过程以避免使用索引扫描,而是依赖索引搜索。 在另一个例子中,我发现更新重建查询中使用的特定表的索引会产生重大影响。

查找&更新索引

我已经使用这个SQL来查找和重建适当的索引:

/* 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 :(得分:0)

您的SP是否完全使用动态T-SQL?如果是这样,您将失去缓存执行计划的好处......

如果失败了,用于运行SP vs T-SQL的连接是否以相同的方式配置?速度差是否一致,或者SP在缓和后第一次运行时是否缓慢?

答案 3 :(得分:0)

这个问题通过Greg Larsen展示的不同方法得到解决 访问https://www.simple-talk.com/sql/t-sql-programming/parameter-sniffing/