SQL Server:查询速度快,但程序慢

查询运行速度很快:

DECLARE @SessionGUID uniqueidentifier SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908' SELECT * FROM Report_Opener WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank 

子树成本:0.502

但是,在存储过程中放入相同的SQL运行速度很慢,并且执行计划完全不同

 CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS SELECT * FROM Report_Opener WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank EXECUTE ViewOpener @SessionGUID 

子树成本:19.2

我跑了

 sp_recompile ViewOpener 

它仍然运行相同(严重),我也改变了存储过程

 CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS SELECT *, 'recompile please' FROM Report_Opener WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank 

再回来,试图欺骗它重新编译。

我已经删除并重新创build了存储过程,以便生成一个新的计划。

我已经尝试强制重新编译, 通过使用诱饵variables来防止参数嗅探

 CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS DECLARE @SessionGUIDbitch uniqueidentifier SET @SessionGUIDbitch = @SessionGUID SELECT * FROM Report_Opener WHERE SessionGUID = @SessionGUIDbitch ORDER BY CurrencyTypeOrder, Rank 

我也试着用WITH RECOMPILE定义存储过程:

 CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier WITH RECOMPILE AS SELECT * FROM Report_Opener WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank 

所以它的计划从来没有caching,我已经尝试强制执行重新编译:

 EXECUTE ViewOpener @SessionGUID WITH RECOMPILE 

哪些没有帮助。

我已经尝试将过程转换为dynamicSQL:

 CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier WITH RECOMPILE AS DECLARE @SQLString NVARCHAR(500) SET @SQLString = N'SELECT * FROM Report_OpenerTest WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank' EXECUTE sp_executesql @SQLString, N'@SessionGUID uniqueidentifier', @SessionGUID 

哪些没有帮助。

实体“ Report_Opener ”是一个未被索引的视图。 该视图仅引用基础表。 没有表格包含计算列,索引或其他。

对于它的地狱,我试图创build视图

 SET ANSI_NULLS ON SET QUOTED_IDENTIFER ON 

这并没有解决它。

这是怎么回事?

  • 查询速度很快
  • 将查询移动到视图,并从视图中select是很快的
  • 从存储过程的视图中select是40倍慢?

我尝试将视图的定义直接转移到存储过程中(违反了3个业务规则,并且打破了一个重要的封装),这使得它的速度只有6倍左右。

为什么存储过程版本太慢? 什么可以解释为什么SQL Server运行即席SQL比另一种特殊的SQL更快?

我真的不喜欢

  • 在代码中embeddedSQL
  • 完全改变代码

     Microsoft SQL Server 2000 - 8.00.2050 (Intel X86) Mar 7 2008 21:29:56 Copyright (c) 1988-2003 Microsoft Corporation Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2) 

但是,如果SQL Server无法像运行查询的SQL Sever那样快速运行,那么究竟是什么原因,如果不是参数嗅探的话。


我的下一个尝试是将StoredProcedureA调用StoredProcedureB调用StoredProcedureC调用StoredProcedureD来查询视图。

如果失败了,请让存储过程调用存储过程,调用UDF,调用UDF,调用存储过程,调用UDF来查询视图。


综上所述,以下QA运行速度很快,但放入存储过程时速度较慢:

原本的:

 --Runs fine outside of a stored procedure SELECT * FROM Report_OpenerTest WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank 

sp_executesql

 --Runs fine outside of a stored procedure DECLARE @SQLString NVARCHAR(500) SET @SQLString = N'SELECT * FROM Report_OpenerTest WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank' EXECUTE sp_executesql @SQLString, N'@SessionGUID uniqueidentifier', @SessionGUID 

EXEC(@sql)

 --Runs fine outside of a stored procedure DECLARE @sql NVARCHAR(500) SET @sql = N'SELECT * FROM Report_OpenerTest WHERE SessionGUID = '''+CAST(@SessionGUID AS varchar(50))+''' ORDER BY CurrencyTypeOrder, Rank' EXEC(@sql) 

执行计划

计划:

  |--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC)) |--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[CurrencyType] |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID])) |--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currencies]. | |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH) | |--Nested Loops(Left Outer Join) | | |--Bookmark Lookup(BOOKMARK:([Bmk1016]), OBJECT:([GrobManagementSystemLive].[dbo].[Windows])) | | | |--Nested Loops(Inner Join, OUTER REFERENCES:([Openers].[WindowGUID])) | | | |--Bookmark Lookup(BOOKMARK:([Bmk1014]), OBJECT:([GrobManagementSystemLive].[dbo].[Openers])) | | | | |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_SessionGUID]), SEEK:([Openers].[SessionGUID]=[@SessionGUID]) ORDERED FORWARD) | | | |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows]), SEEK:([Windows].[WindowGUID]=[Openers].[WindowGUID]) ORDERED FORWARD) | | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType])) | |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Currenc |--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID])) |--Stream Aggregate(DEFINE:([Expr1006]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]='ctCanadianCoin') OR [ |--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH) |--Nested Loops(Inner Join) | |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD) | |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD) |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD) 

不好的计划

  |--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC)) |--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[Currency |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID])) |--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currenc | |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH) | |--Filter(WHERE:([Openers].[SessionGUID]=[@SessionGUID])) | | |--Concatenation | | |--Nested Loops(Left Outer Join) | | | |--Table Spool | | | | |--Hash Match(Inner Join, HASH:([Windows].[WindowGUID])=([Openers].[WindowGUID]), RESIDUAL:([Windows].[WindowGUID]=[Openers].[WindowGUID])) | | | | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows_CageGUID])) | | | | |--Table Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Openers])) | | | |--Table Spool | | | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType])) | | |--Compute Scalar(DEFINE:([Openers].[OpenerGUID]=NULL, [Openers].[SessionGUID]=NULL, [Windows].[UseChipDenominations]=NULL)) | | |--Nested Loops(Left Anti Semi Join) | | |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType])) | | |--Row Count Spool | | |--Table Spool | |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Cu |--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID])) |--Stream Aggregate(DEFINE:([Expr1006]=SUM([partialagg1034]), [Expr1007]=SUM([partialagg1035]), [Expr1008]=SUM([partialagg1036]), [Expr1009]=SUM([partialagg1037]), [Expr1010]=SUM([partialagg1038]), [Expr1011]=SUM([partialagg1039] |--Nested Loops(Inner Join) |--Stream Aggregate(DEFINE:([partialagg1034]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]=' | |--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH) | |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD) | |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD) |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD) 

坏的是急于破坏600万行; 另一个不是。

注意:这不是一个关于调整查询的问题。 我有一个快速运行的查询。 我只想SQL Server从一个存储过程快速运行。

我和原来的海报有同样的问题,但引用的答案并没有解决我的问题。 从存储过程查询仍然运行非常缓慢。

我在这里find了另一个答案“参数嗅探” ,谢谢Omnibuzz。 归结为在存储过程查询中使用“局部variables”,但阅读原来的更多的理解,这是一个伟大的写作。 例如

缓慢的方式:

 CREATE PROCEDURE GetOrderForCustomers(@CustID varchar(20)) AS BEGIN SELECT * FROM orders WHERE customerid = @CustID END 

快速的方法:

 CREATE PROCEDURE GetOrderForCustomersWithoutPS(@CustID varchar(20)) AS BEGIN DECLARE @LocCustID varchar(20) SET @LocCustID = @CustID SELECT * FROM orders WHERE customerid = @LocCustID END 

希望这可以帮助其他人,这样做会将我的执行时间从5分钟减less到大约6-7秒。

我发现这个问题,下面是存储过程缓慢和快速版本的脚本:

dbo.ViewOpener__RenamedForCruachan__Slow.PRC

 SET QUOTED_IDENTIFIER OFF GO SET ANSI_NULLS OFF GO CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Slow @SessionGUID uniqueidentifier AS SELECT * FROM Report_Opener_RenamedForCruachan WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank GO SET QUOTED_IDENTIFIER OFF GO SET ANSI_NULLS ON GO 

dbo.ViewOpener__RenamedForCruachan__Fast.PRC

 SET QUOTED_IDENTIFIER OFF GO SET ANSI_NULLS ON GO CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Fast @SessionGUID uniqueidentifier AS SELECT * FROM Report_Opener_RenamedForCruachan WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank GO SET QUOTED_IDENTIFIER OFF GO SET ANSI_NULLS ON GO 

如果你没有发现差异,我不怪你。 这个差别完全不在存储过程中。 不同的是,将一个快速的0.5成本查询转换成一个包含600万行的热门假脱机文件:

慢: SET ANSI_NULLS OFF

快速: SET ANSI_NULLS ON


这个答案也可以做出有意义的,因为视图确实有一个连接子句,说:

 (table.column IS NOT NULL) 

所以有一些NULL参与。


通过返回查询分析器进行进一步的说明,并运行

 SET ANSI_NULLS OFF 

 DECLARE @SessionGUID uniqueidentifier SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908' 

 SELECT * FROM Report_Opener_RenamedForCruachan WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank 

查询很慢。


所以这个问题不是因为查询是从存储过程运行的。 问题是企业pipe理器的连接默认选项是ANSI_NULLS off而不是ANSI_NULLS on ,这是QA的默认选项。

Microsoft在KB296769中承认这一事实(错误:不能使用SQL企业pipe理器创build包含链接的服务器对象的存储过程)。 解决方法是在存储过程对话框中包含ANSI_NULLS选项:

 Set ANSI_NULLS ON Go Create Proc spXXXX as .... 

为你的数据库做这个。 我有同样的问题 – 它在一个数据库中工作正常,但是当我使用SSIS导入(而不是通常的恢复)将此数据库复制到另一个时,这个问题发生在我的大多数存储过程中。 所以在Google上搜了一些之后,我find了Pinal Dave的博客(顺便说一下,我遇到了他的大部分post,并且非常感谢Pinal Dave) 。

我在我的数据库上执行下面的查询,它纠正了我的问题:

 EXEC sp_MSforeachtable @command1="print '?' DBCC DBREINDEX ('?', ' ', 80)" GO EXEC sp_updatestats GO 

希望这可以帮助。 只是通过帮助我的人的帮助。

这一次你发现你的问题。 如果下一次你不那么幸运,不能解决,你可以使用计划冻结,并停止担心错误的执行计划。

我遇到了这个问题。 我的查询看起来像这样:

 select a, b, c from sometable where date > '20140101' 

我的存储过程是这样定义的:

 create procedure my_procedure (@dtFrom date) as select a, b, c from sometable where date > @dtFrom 

我改变了数据typesdatetime和瞧! 从30分钟到1分钟!

 create procedure my_procedure (@dtFrom datetime) as select a, b, c from sometable where date > @dtFrom 

我正面临着同样的问题,这个职位对我非常有帮助,但没有任何贴子解答我的具体问题。 我想发布为我工作的解决scheme,希望能帮助别人。

https://stackoverflow.com/a/24016676/814299

在查询结束时,添加OPTION(OPTIMIZE FOR(@now UNKNOWN))

您是否尝试过重buildReport_Opener表中的统计信息和/或索引。 如果统计数据仍显示数据库首次启动时的数据,那么所有的SP重新编译都是不值得的。

初始查询本身的工作很快,因为优化器可以看到参数永远不会为空。 在SP的情况下,优化器不能确定参数永远不会为空。

虽然我通常反对它(虽然在这种情况下,似乎你有一个真正的原因),你是否尝试过在查询的SP版本上提供任何查询提示? 如果SQL Server在这两个实例中准备一个不同的执行计划,您可以使用一个提示告诉它使用哪个索引,以便计划匹配第一个?

对于一些例子, 你可以去这里 。

编辑:如果你可以发布你的查询计划在这里,也许我们可以识别计划,告诉一些区别。

SECOND:更新链接为SQL-2000特定。 你将不得不向下滚动,但还有一个名为“表提示”,这是你正在寻找。

THIRD:“Bad”查询似乎忽略了“Openers”表中的[IX_Openers_SessionGUID] – 任何添加INDEX提示来强制使用该索引的机会都会改变?

这可能不太可能,但鉴于你观察到的行为是不寻常的,它需要检查,没有人提到它。

你是否确定所有的物品都是dbo所有的,你没有自己拥有的stream氓拷贝或者在场的不同用户?

偶尔当我看到奇怪的行为,这是因为实际上有一个对象的两个副本,你得到哪一个取决于指定什么和你login谁。 例如,完全有可能拥有两个相同名称但拥有不同所有者的视图或过程的副本 – 在没有以dbo身份login到数据库时可能出现的情况,并忘记将dbo指定为对象所有者时你创build对象。

注意在文本中你正在运行一些东西而不指定所有者,例如

 sp_recompile ViewOpener

例如,如果有dbo和[某个其他用户]拥有的两个viewOpener副本,那么如果不指定,则实际重新编译的是依赖于环境的情况。 与Report_Opener视图同上 – 如果有两个副本(并且它们可能在规范或执行计划上有所不同),那么使用什么取决于环境 – 而且您不指定所有者,您的即席查询可能会使用一个和编译的程序可以使用其他的。

正如我所说,这可能不太可能,但它是可能的,应该检查,因为你的问题可能是你只是在错误的地方寻找错误。

这听起来很愚蠢,从SessionGUID这个名字看起来很明显,但是这个列是Report_Opener的唯一标识符吗? 如果没有,你可能想尝试将其转换为正确的types,并给它一个镜头或声明你的variables为正确的types。

作为片子的一部分而创build的计划可能不直观,在大桌子上进行内部演员。

我有另一个想法。 如果你创build这个基于表格的函数呢?

 CREATE FUNCTION tbfSelectFromView ( -- Add the parameters for the function here @SessionGUID UNIQUEIDENTIFIER ) RETURNS TABLE AS RETURN ( SELECT * FROM Report_Opener WHERE SessionGUID = @SessionGUID ORDER BY CurrencyTypeOrder, Rank ) GO 

然后使用下面的语句从中select(甚至把它放在你的SP中):

 SELECT * FROM tbfSelectFromView(@SessionGUID) 

看起来发生了什么事(每个人都已经评论过),SQL Server只是假设某个地方是错的,也许这会迫使它纠正这个假设。 我讨厌添加额外的步骤,但我不知道还有什么可能导致它。

今天早上我有同样的问题,非常奇怪,一个运行存储过程的经典asp页面需要1分钟+。 运行asp页面的SQL在MSSMS中运行了2秒…在花费年龄调查执行计划之前,我想我只是通过ALTER PROCEDURE(插入注释行)对SP进行一个小改动。 在运行ALTER语句之后,SP在IIS和MSSMS中立即执行。 我猜测一个SP的标准重新编译会做的伎俩:(关于重新编译的方便的信息:)

http://www.devx.com/tips/Tip/13386

重build相关表格上的索引帮助我解决了这个问题

– 这是解决scheme:

 create procedure GetOrderForCustomers(@CustID varchar(20)) as begin select * from orders where customerid = ISNULL(@CustID, '') end 

– 而已