web-dev-qa-db-ja.com

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つのビジネスルールに違反し、重要なカプセル化を破りました)。

なぜストアドプロシージャのバージョンが非常に遅いのですか?アドホックSQLを実行しているSQL Serverが、異なる種類のアドホックSQLよりも高速である理由は何でしょうか?

私は本当にしたくない

  • 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 Serverです。


次に、StoredProcedureAname__を呼び出してStoredProcedureBname__を呼び出してStoredProcedureCname__を呼び出して、ビューを照会するためにStoredProcedureDname__を呼び出します。

それに失敗すると、ストアドプロシージャがストアドプロシージャを呼び出し、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)

実行計画

good計画:

      |--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)

badプラン

       |--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万行を熱心にスプールしています。もう1つはそうではありません。

注:これはクエリのチューニングに関する質問ではありません。超高速で実行するクエリがあります。ストアドプロシージャからSQL Serverを高速に実行したいだけです。

234
Ian Boyd

私は問題を見つけました、ここにストアドプロシージャの遅いバージョンと速いバージョンのスクリプトがあります:

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)

そのため、いくつかのNULLsが関係しています。


Query 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

また、クエリが遅いです。


クエリはストアドプロシージャから実行されているため、問題ではありません。問題は、Enterprise Managerの接続デフォルトオプションが、QAのデフォルトであるANSI_NULLS offではなく、ANSI_NULLS onであることです。

Microsoftは、この事実を KB296769 (バグ:SQL Enterprise Managerを使用して、リンクサーバーオブジェクトを含むストアドプロシージャを作成することはできません)で認めています。回避策は、ストアドプロシージャダイアログにANSI_NULLSオプションを含めることです。

Set ANSI_NULLS ON
Go
Create Proc spXXXX as
....
126
Ian Boyd

私は元のポスターと同じ問題を抱えていましたが、引用された答えは私にとって問題を解決しませんでした。ストアドプロシージャからのクエリの実行は依然として非常に遅くなりました。

私は別の答えを見つけました ここでは「パラメータスニッフィング」 、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秒に短縮されました。

361
Adam Marshall

データベースに対してこれを行います。私は同じ問題を抱えています-1つのデータベースで正常に動作しますが、SSISインポート(通常の復元ではない)を使用してこのデータベースを別のデータベースにコピーすると、ほとんどのストアドプロシージャでこの問題が発生します。それで、もう少しグーグルで調べた後、私は Pinal Daveのブログ(ところで彼の投稿のほとんどに出会って、Pinal Daveに感謝しました) を見つけました。

データベースで次のクエリを実行すると、問題が修正されました。

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

お役に立てれば。助けてくれた他の人からの助けを渡すだけです。

16
Carenski

私は同じ問題に直面しており、この投稿は私にとって非常に役に立ちましたが、投稿された回答のどれも私の特定の問題を解決しませんでした。他の人の助けになることを期待して、私のために働いた解決策を投稿したかった。

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

クエリの最後に、OPTION(OPTIMIZE FOR(@now UNKNOWN))を追加します

10
jessieloo

今回は問題が見つかりました。次回、運が悪くて理解できない場合は、 plan freezing を使用して、間違った実行計画の心配をやめることができます。

4
A-K

この問題が発生していました。私のクエリは次のようになりました:

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

データ型を日時と出来上がりに変更しました! 30分から1分になりました!

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

これはばかげて聞こえるかもしれませんし、SessionGUIDという名前から明らかなようですが、この列はReport_Openerの一意の識別子ですか?そうでない場合は、正しい型にキャストして試してみるか、変数を正しい型に宣言してください。

Sprocの一部として作成されたプランは、直感的に機能せず、大きなテーブルで内部キャストを行う場合があります。

1
David Rendall

これはおそらくありそうにありませんが、観察された動作が異常であるため、確認する必要があり、誰もそれについて言及していません。

絶対すべてのオブジェクトがdboによって所有されており、自分または別のユーザーが所有する不正コピーがないことを確認しますか?

たまに奇妙な振る舞いを見たとき、実際にはオブジェクトのコピーが2つあり、どちらを取得するかは、指定されたものとログオンしているユーザーによって異なります。たとえば、ビューまたはプロシージャの2つのコピーを同じ名前で異なる所有者が所有することは完全に可能です-dboとしてデータベースにログオンしておらず、dboをオブジェクト所有者として指定し忘れた場合に発生する可能性がある状況オブジェクトを作成します。

テキストでは、所有者を指定せずにいくつかのことを実行していることに注意してください、例えば

 sp_recompile ViewOpener 

たとえば、dboと[他のユーザー]が所有するviewOpenerの2つのコピーがある場合、指定しない場合に実際に再コンパイルするのは状況に依存します。 Report_Openerビューと同じ-2つのコピーがある場合(仕様または実行プランが異なる場合)、使用される内容は状況に依存します-所有者を指定しない場合、アドホッククエリが1つを使用し、コンパイルされたプロシージャは、他を使用する可能性があります。

私が言うように、それはおそらくありそうもないことですが、可能性があり、チェックする必要があります。問題は、単に間違った場所でバグを探していることである可能性があるからです。

1
Cruachan

私は通常は反対ですが(この場合、真の理由があるようですが)、クエリのSPバージョンでクエリヒントを提供しようとしましたか? SQL Serverがこれらの2つのインスタンスで異なる実行計画を準備している場合、使用するインデックスを指示するヒントを使用して、最初の計画と一致するようにできますか?

いくつかの例については、 ここに行くことができます

編集:ここにクエリプランを投稿できる場合は、おそらく、伝えているプラ​​ンの違いを特定できます。

SECOND:リンクをSQL-2000固有に更新しました。方法を下にスクロールする必要がありますが、探しているのは「Tableヒント」というタイトルの2番目のタイトルです。

3番目:「悪い」クエリは、「Openers」テーブルの[IX_Openers_SessionGUID]を無視しているようです-インデックスヒントを追加してインデックスの使用を強制する可能性がありますか?

1
SqlRyan

Report_Openerテーブルの統計やインデックスを再構築しようとしましたか。 SPのすべての再コンパイルは、データベースが最初に起動されたときからのデータがまだ統計に表示されている場合、何の価値もありません。

オプティマイザーはパラメーターがnullにならないことを確認できるため、最初のクエリ自体は迅速に機能します。 SPの場合、オプティマイザーはパラメーターがnullにならないことを確認できません。

1
AnthonyWJones

別のアイデアがあります。このテーブルベースの関数を作成した場合:

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

そして、次の文を使用してそれから選択します(これをSPに入れます):

SELECT *
FROM tbfSelectFromView(@SessionGUID)

何が起こっているのか(誰もがすでにコメントしているように見える)は、SQL Serverがどこか間違っていると仮定しているだけであり、おそらくこれが仮定を修正することを強制しているようです。余分なステップを追加するのは嫌いですが、他に何が原因であるかはわかりません。

0
SqlRyan

-解決策は次のとおりです。

create procedure GetOrderForCustomers(@CustID varchar(20))

as

begin

select * from orders

where customerid = ISNULL(@CustID, '')

end

- それでおしまい

0
Koldoon