2009-01-13 75 views
201

查询运行速度快:SQL服务器:查询速度快,但速度缓慢的过程

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 

,然后再返回,试图欺骗真它进入重新编译。

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

我试图重新编译迫,和防止参数嗅探,通过使用诱饵变量:

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 

因此,它的计划永远不会被缓存,并且我试过在执行时强制重新编译:

EXECUTE ViewOpener @SessionGUID WITH RECOMPILE 

哪没有帮助。

我试过的程序转换为动态SQL:

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”是未被索引的视图。该视图仅引用基础表。没有表格包含计算列,索引或其他。

对于它的地狱我试图与

SET ANSI_NULLS ON 
SET QUOTED_IDENTIFER ON 

没有解决它创建视图。

它是如何

  • 查询是快速
  • 移动所述查询的图,从该视图从视图从存储过程中选择是快速
  • 选择是40倍慢?

我试着将视图的定义直接移动到存储过程中(违反了3个业务规则,并且破坏了一个重要的封装),并且仅使其速度降低了大约6倍。

为什么存储过程版本太慢?与其他类型的特别SQL相比,运行临时SQL的SQL Server可能会更快吗?

我真的不想

  • 嵌入代码的SQL
  • 变化的代码在所有

    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从存储过程中快速运行。

回答

120

我发现这个问题,这里的存储过程的快慢版本的脚本:

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 

如果你没没有发现差异,我不怪你。根本不在存储过程中。

慢:SET ANSI_NULLS OFF

快速:SET ANSI_NULLS ON


这个答案也可以是把一个快0.5费用查询到一个没有600万行的渴望阀芯的区别因为视图确实有一个连接子句:

(table.column IS NOT NULL) 

所以涉及到一些NULL


的解释是通过返回到查询Analizer和运行

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 

而查询很慢。


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

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

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

我仍然不明白如何转换ANSI_NULLS ON使得性能有如此巨大的差异。 – 2013-07-16 16:42:18

+0

@ Ek0nomik因为'ANSI_NULLS OFF'时视图内部的JOIN子句具有不同的含义。突然行匹配,导致优化器完全不同地运行查询。想象一下,不是排除所有行中的99.9%,而是突然返回。 – 2013-07-16 23:00:51

1

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

对于一些示例,you can go here

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

SECOND:更新链接为SQL-2000特定。您必须向下滚动某种方式,但还有另一个标题为“表格提示”,这就是您要查找的内容。

THIRD:“坏”查询似乎忽略了“Openers”表上的[IX_Openers_SessionGUID] - 任何添加INDEX提示以强制它使用该索引的机会会改变什么?

+0

在参考的最有用的查询提示并非适用于SQL 2000这是这里讨论的版本。 – AnthonyWJones 2009-01-13 21:59:43

+0

此外,需要什么提示?当我特别运行它时,SQL Server能够发现它没有问题。 – 2009-01-13 22:05:38

+0

当然,这也是我的经验。然而,在这种情况下,他说这是一个完全不同的执行计划。也许有一个特殊的索引,但由于某些原因在proc中被忽略。他可以强制SQL Server使用带有“INDEX”提示的索引。 – SqlRyan 2009-01-13 22:12:20

1

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

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

+0

有没有办法在存储过程声明中指出i参数不能为空?这不是由sp_executesql修复的东西吗? – 2009-01-13 22:07:03

+0

在一个单词中,不在2000年。2005年添加了一个查询提示,您可以在其中为参数提供示例值,优化器会优化,就好像它知道总是使用该参数一样。话虽如此,我通常发现这种事情是一个统计问题。 – AnthonyWJones 2009-01-13 22:14:57

+0

如果是统计问题,当我运行ad-hoc,sp_executesql,exec()时,它们可以在QA中正常工作。为什么当存储过程包含临时sql,sp_executesql,exec()时,它们都运行得不好? – 2009-01-13 22:18:18

0

我有另一个想法。如果你创建这个基于表格的功能:使用下面的语句(即使把这个在你的SP)

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 * 
FROM tbfSelectFromView(@SessionGUID) 

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

1

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

你是绝对确定所有对象都是dbo所有,你没有自己拥有的流氓拷贝或者不同的用户在场吗?

只是偶尔当我看到奇怪的行为,这是因为实际上有一个对象的两个副本,你得到哪一个取决于指定的内容和你登录的人。例如,完全有可能拥有两个具有相同名称但由不同所有者拥有的视图或过程的副本 - 在没有以dbo身份登录到数据库的情况下可能会出现的情况,并忘记将dbo指定为对象所有者时你创建该对象。

在注意在文本你没有指定所有者运行一些东西,比如

 
sp_recompile ViewOpener 

如果举例来说,其中dbo所拥有viewOpener目前的两个副本和[其他一些用户]那么哪一个你如果你没有指定,实际上重新编译依赖于情况。与Report_Opener视图同上 - 如果存在两个副本(并且它们可能在规范或执行计划中有所不同),那么使用的内容取决于具体情况 - 因为您没有指定所有者,所以您的即席查询可能会使用一个和编译的程序可以使用其他的。

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

310

我有同样的问题,因为原来的海报,但所引用的答案并没有解决这个问题对我来说。查询仍然从存储过程中运行得非常慢。

我发现了另一个答案here "Parameter Sniffing",谢谢Omnibuzz。归结为在存储过程查询中使用“局部变量”,但阅读原始文件以获得更多理解,这是一个很好的写法。例如

慢速方式:

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+分钟至约6-7秒。

3

这次你发现你的问题。如果下一次你不那么幸运并且无法弄清楚,你可以使用plan freezing并停止担心错误的执行计划。

15

对您的数据库执行此操作。我有同样的问题 - 它在一个数据库中工作正常,但是当我使用SSIS导入(而不是通常的恢复)将此数据库复制到另一个时,这个问题发生在我的大多数存储过程中。所以google搜索了一些之后,我发现了blog of Pinal Dave (which btw, I encountered most of his post and did help me a lot so thanks Pinal Dave)

我执行我的数据库之下查询,并修正它我的问题:

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

希望这有助于。只是传递帮助我的其他人的帮助。

0

上重建的有关表的索引帮助我度过这个问题

0

- 这里是解决方案:

create procedure GetOrderForCustomers(@CustID varchar(20)) 

as 

begin 

select * from orders 

where customerid = ISNULL(@CustID, '') 

end 

- 这就是它

3

我遇到此问题。我的查询看起来是这样的:

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 

我改变了数据类型为datetime,瞧!从30分钟到1分钟!

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

这可能听起来很傻,并从名称SessionGUID似乎是显而易见的,但列在Report_Opener唯一标识符?如果没有,你可能想尝试将它转换为正确的类型,并给它一个镜头或声明你的变量为正确的类型。

作为sproc一部分创建的计划可能不直观,并在大型桌子上进行内部演员表演。

4

我正面临同样的问题&这篇文章对我很有帮助,但没有任何发布的答案能解决我的具体问题。我想发布对我有用的解决方案,希望它能帮助别人。

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

在查询的结尾添加OPTION(OPTIMIZE FOR(@now未知))