【问题标题】:Why would a Stored Procedure run slower than naked T-SQL?为什么存储过程会比裸 T-SQL 运行得慢?
【发布时间】:2010-08-27 00:22:14
【问题描述】:

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

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

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

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

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

这是一个巨大的差异,并且没有明显的功能变化。这可能是什么原因造成的?

更新

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

这对于解决眼前的问题非常好,但我仍然想知道“为什么”。

【问题讨论】:

  • 如果您能描述一下 SP 和 TSQL 正在做什么,看看您的 SP 是否存在效率低下的问题,将会有所帮助。
  • 您是唯一使用该实例的人吗?时间可能是因为服务器负载...
  • @James:同意,但我认为代码的细节过于商业化,没有多大用处。没有什么不寻常的,只是一堆连接,排序依据,以及选择列表中的一些连接。
  • @OMG Ponis:你是说数据库实例吗?在我运行期间,此(测试)服务器上没有其他任何事情发生。

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


【解决方案1】:

这可能正是由于在 SP 中执行计划被缓存并且对于数据集而言不是最优的这一事实。当数据集很大程度上取决于参数或在调用之间发生很大变化时,最好在“create proc”中指定“with recompile”。您在重新编译时损失了几分之一秒,但在执行时可能会赢得几分钟。

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

【讨论】:

  • 你没有足够的积分来评论(这里,我会帮你)
  • 呵呵,有时候可以。我不是为了积分,但谢谢,tster。
  • 听起来很有可能。我会看看我是否可以测试它;如果可行,那么我会将您的标记为已接受。
【解决方案2】:

我最近几次遇到完全相同的问题(使用 MS-SQL 2008)。特定的存储过程运行起来会非常慢(几分钟),但粘贴到 SSMS 中的相同 SQL 只需几秒钟。

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

比较执行计划

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

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

从 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

【讨论】:

    【解决方案3】:

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

    否则,用于运行 SP 与 T-SQL 的连接是否以相同的方式配置?速度差异是一致的还是SP在修改后第一次运行时那么慢?

    【讨论】:

    • 请阅读The Curse and Blessing of Dynamic SQL,否则您会知道EXEC 不会缓存查询计划,而EXEC sp_executesql 确实...
    • 连接应该相同;我正在使用默认设置在 SQL Management Studio 窗口中运行我的测试。
    • 速度数字是一致的(至少预重新索引);我会更新我的问题以反映这一点。顺便说一句,感谢您的帮助。
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-01
      • 1970-01-01
      • 2015-04-21
      • 1970-01-01
      • 2012-03-15
      相关资源
      最近更新 更多