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

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

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

我试过使用一个诱饵变量强制重新编译, 并防止参数嗅探

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


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

    我在这里找到了另一个答案“参数嗅探”,谢谢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秒。


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

    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成本查询转换成一个包含6百万行的热切假脱机:

    慢: 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
    

    查询很慢。


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

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

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

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

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

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

    希望这可以帮助。 只是传递帮助我的其他人的帮助。

    链接地址: http://www.djcxy.com/p/62239.html

    上一篇: SQL Server: Query fast, but slow from procedure

    下一篇: SQL Query gets slower with each iteration?