SQL Server: Abfrage schnell, aber langsam von der Prozedur


257

Eine Abfrage wird schnell ausgeführt:

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

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

Teilbaumkosten: 0,502

Das Einfügen derselben SQL in eine gespeicherte Prozedur wird jedoch langsam und mit einem völlig anderen Ausführungsplan ausgeführt

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

EXECUTE ViewOpener @SessionGUID

Teilbaumkosten: 19.2

Ich bin gelaufen

sp_recompile ViewOpener

Und es läuft immer noch genauso (schlecht), und ich habe auch die gespeicherte Prozedur in geändert

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

Und wieder zurück, um es wirklich zum Neukompilieren zu bringen.

Ich habe die gespeicherte Prozedur gelöscht und neu erstellt, damit sie einen neuen Plan generiert.

Ich habe versucht, Neukompilierungen zu erzwingen und das Schnüffeln von Parametern mithilfe einer Täuschungsvariablen zu verhindern:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS

DECLARE @SessionGUIDbitch uniqueidentifier
SET @SessionGUIDbitch = @SessionGUID

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

Ich habe auch versucht, die gespeicherte Prozedur zu definieren WITH RECOMPILE:

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

Damit der Plan nie zwischengespeichert wird, habe ich versucht, bei der Ausführung eine Neukompilierung zu erzwingen:

EXECUTE ViewOpener @SessionGUID WITH RECOMPILE

Was nicht geholfen hat.

Ich habe versucht, die Prozedur in dynamisches SQL zu konvertieren:

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

Was nicht geholfen hat.

Die Entität " Report_Opener" ist eine Ansicht, die nicht indiziert ist. Die Ansicht verweist nur auf zugrunde liegende Tabellen. Keine Tabelle enthält berechnete Spalten, indiziert oder anderweitig.

Zum Teufel habe ich versucht, die Ansicht mit zu erstellen

SET ANSI_NULLS ON
SET QUOTED_IDENTIFER ON

Das hat es nicht behoben.

Wie kommt es, dass

  • Die Abfrage ist schnell
  • Das Verschieben der Abfrage in eine Ansicht und das Auswählen aus der Ansicht ist schnell
  • Die Auswahl aus der Ansicht einer gespeicherten Prozedur ist 40x langsamer.

Ich habe versucht, die Definition der Ansicht direkt in die gespeicherte Prozedur zu verschieben (Verletzung von 3 Geschäftsregeln und Aufheben einer wichtigen Kapselung), und das macht sie nur etwa 6x langsamer.

Warum ist die Version der gespeicherten Prozedur so langsam? Was kann möglicherweise dazu führen, dass SQL Server Ad-hoc-SQL schneller ausführt als eine andere Art von Ad-hoc-SQL?

Ich würde wirklich lieber nicht

  • Betten Sie die SQL in Code ein
  • Ändern Sie den Code überhaupt

    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)
    

Aber was kann dafür verantwortlich sein, dass SQL Server nicht so schnell ausgeführt werden kann wie SQL Server, der eine Abfrage ausführt, wenn nicht das Parameter-Sniffing.


Mein nächster Versuch wird es sein, hat StoredProcedureAAnruf StoredProcedureBAnruf StoredProcedureCAnruf StoredProcedureDdie Ansicht abzufragen.

Wenn dies nicht der Fall ist, lassen Sie die gespeicherte Prozedur eine gespeicherte Prozedur aufrufen, eine UDF aufrufen, eine UDF aufrufen, eine gespeicherte Prozedur aufrufen und eine UDF aufrufen, um die Ansicht abzufragen.


Zusammenfassend lässt sich sagen, dass die folgenden Schritte schnell von der Qualitätssicherung ausgeführt werden, jedoch langsam, wenn sie in eine gespeicherte Prozedur gestellt werden:

Das Original:

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

Ausführungspläne

Der gute Plan:

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

Der schlechte Plan

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

Der Böse spult eifrig 6 Millionen Zeilen; der andere ist es nicht.

Hinweis: Dies ist keine Frage zum Optimieren einer Abfrage. Ich habe eine Abfrage, die blitzschnell ausgeführt wird. Ich möchte nur, dass SQL Server von einer gespeicherten Prozedur aus schnell ausgeführt wird.


Ich bemerke jedes Mal, wenn Sie einen Parameter nehmen und ihn einem anderen zuweisen und ihn später in einer Abfrage verwenden. Dies kann passieren und wie die Antwort andeutet, kann Optimize for @ "someparamname" unknown funktionieren.
JustDave

Antworten:


404

Ich hatte das gleiche Problem wie das Originalplakat, aber die zitierte Antwort löste das Problem für mich nicht. Die Abfrage wurde von einer gespeicherten Prozedur immer noch sehr langsam ausgeführt.

Ich habe hier eine andere Antwort gefunden "Parameter Sniffing" , danke Omnibuzz. Es läuft darauf hinaus, "lokale Variablen" in Ihren Abfragen für gespeicherte Prozeduren zu verwenden, aber lesen Sie das Original, um es besser zu verstehen. Es ist eine großartige Zusammenfassung. z.B

Langsamer Weg:

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

Der schnelle Weg:

CREATE PROCEDURE GetOrderForCustomersWithoutPS(@CustID varchar(20))
AS
BEGIN
    DECLARE @LocCustID varchar(20)
    SET @LocCustID = @CustID

    SELECT * 
    FROM orders
    WHERE customerid = @LocCustID
END

Ich hoffe, dies hilft jemand anderem. Dadurch wurde meine Ausführungszeit von mehr als 5 Minuten auf etwa 6 bis 7 Sekunden reduziert.


23
+1 Aber das ist sehr seltsam und wirft viele Fragen auf: Sollten wir dies für alle Verfahren tun und wenn nicht, wann?
Gotqn

31
Bin ich der einzige, der von diesem Verhalten verblüfft ist? Lokale Variablen müssen deklariert werden, um das Schnüffeln von Parametern zu verhindern? Sollte SQL Server nicht intelligent genug sein, um dies überhaupt zu verhindern? Dies führt nur dazu, dass das kurzsichtige Design von Microsoft meiner Meinung nach unnötigen Code aufbläht.
l46kok

4
15 min -> 8 sek! Lebensretter
Tony Brix

3
@BennettDill WITH RECOMPILEmachte für mich keinen Unterschied, nur die lokalen Parameter.
mrogers

8
Dies kann jetzt mit dem Abfragehinweis erreicht werden - OPTION (OPTIMIEREN FÜR (@varA UNBEKANNT, @varB UNBEKANNT)
Dave

131

Ich habe das Problem gefunden. Hier ist das Skript der langsamen und schnellen Versionen der gespeicherten Prozedur:

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

Wenn Sie den Unterschied nicht bemerkt haben, beschuldige ich Sie nicht. Der Unterschied liegt überhaupt nicht in der gespeicherten Prozedur. Der Unterschied, der aus einer schnellen Abfrage mit 0,5 Kosten eine Abfrage mit einer eifrigen Spule von 6 Millionen Zeilen macht:

Langsam: SET ANSI_NULLS OFF

Schnell: SET ANSI_NULLS ON


Diese Antwort könnte auch sinnvoll sein, da die Ansicht eine Join-Klausel enthält, die besagt:

(table.column IS NOT NULL)

Es sind also einige NULLbeteiligt.


Die Erklärung wird weiter bewiesen, indem Sie zu Query Analizer zurückkehren und ausführen

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

Und die Abfrage ist langsam.


Das Problem liegt also nicht darin, dass die Abfrage von einer gespeicherten Prozedur ausgeführt wird. Das Problem ist , dass Enterprise Manager der Verbindung Standardoption ist ANSI_NULLS off, und nicht ANSI_NULLS on, das ist Standard QA.

Microsoft erkennt diese Tatsache in KB296769 an (BUG: SQL Enterprise Manager kann nicht zum Erstellen gespeicherter Prozeduren mit verknüpften Serverobjekten verwendet werden). Die Problemumgehung besteht darin, die ANSI_NULLSOption in das Dialogfeld für gespeicherte Prozeduren aufzunehmen:

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

2
Ich verstehe immer noch nicht, wie das Drehen ANSI_NULLS ONeinen so enormen Leistungsunterschied macht.
Justin Helgerson

2
@ Ek0nomik Weil die JOINKlauseln in der Ansicht unterschiedliche Bedeutung haben, wenn ANSI_NULLS OFF. Plötzlich stimmen die Zeilen überein, sodass das Optimierungsprogramm die Abfrage völlig anders ausführt. Stellen Sie sich vor, anstatt 99,9% aller Zeilen zu eliminieren, kehren sie plötzlich zurück.
Ian Boyd

2
Hinweis: ANSI_NULLS OFFist veraltet und als eine schlechte Praxis
jean

2
link "In einer zukünftigen Version von SQL Server ist ANSI_NULLS immer EIN, und alle Anwendungen, die die Option explizit auf AUS setzen, erzeugen einen Fehler. Vermeiden Sie die Verwendung dieser Funktion in neuen Entwicklungsarbeiten und planen Sie, Anwendungen zu ändern, die diese Funktion derzeit verwenden. ""
SOTN

Hat in meinem Fall nicht geholfen.
st_stefanov

19

Tun Sie dies für Ihre Datenbank. Ich habe das gleiche Problem - es funktioniert einwandfrei in einer Datenbank, aber wenn ich diese Datenbank mithilfe des SSIS-Imports in eine andere kopiere (nicht die übliche Wiederherstellung), tritt dieses Problem bei den meisten meiner gespeicherten Prozeduren auf. Nachdem ich noch ein bisschen gegoogelt hatte, fand ich den Blog von Pinal Dave (der übrigens den größten Teil seines Beitrags traf und mir sehr geholfen hat, danke Pinal Dave) .

Ich führe die folgende Abfrage in meiner Datenbank aus und sie hat mein Problem behoben:

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

Hoffe das hilft. Ich gebe nur die Hilfe von anderen weiter, die mir geholfen haben.


2
Nur ein FYI für zukünftige Leser: DBCC REINDEXwurde veraltet, daher sollten Sie nach Alternativen suchen.
Gvee

1
Mein Problem wurde behoben, danke (1m20s bis 2s!). Betreff : DBCC DBREINDEX, MS sagt: "Diese Funktion wird in einer zukünftigen Version von Microsoft SQL Server entfernt. Verwenden Sie diese Funktion nicht in neuen Entwicklungsarbeiten und ändern Sie Anwendungen, die diese Funktion derzeit verwenden, so bald wie möglich. Verwenden Sie stattdessen ALTER INDEX."
AjV Jsy

Ich weiß nicht, ob dies die beste Antwort ist, aber in meinem Fall ist sp_updatestats alles, was es brauchte, also +1
Todd Menier

Vergessen Sie nicht, dass das Wiederherstellen von Indizes Zeit und Platz in Anspruch nehmen kann. Bevor Sie dies auf Ihrem Produktionsserver ausführen, stellen Sie sicher, dass Sie sich eine mögliche Verlangsamung leisten können. Ich würde vorschlagen, in REORGANISIEREN oder REBUILDEN MIT (ONLINE = ON)
Mailand

14

Ich hatte das gleiche Problem und dieser Beitrag war sehr hilfreich für mich, aber keine der veröffentlichten Antworten löste mein spezifisches Problem. Ich wollte die Lösung veröffentlichen, die für mich funktioniert hat, in der Hoffnung, dass sie jemand anderem helfen kann.

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

Fügen Sie am Ende Ihrer Abfrage OPTION hinzu (OPTIMIZE FOR (@now UNKNOWN))


4

Diesmal haben Sie Ihr Problem gefunden. Wenn Sie das nächste Mal weniger Glück haben und es nicht herausfinden können, können Sie Plan Freezing verwenden und sich keine Gedanken mehr über falsche Ausführungspläne machen.


Laut diesem Blogeintrag gilt das Einfrieren von Plänen nur für MS SQL 2005 und höher, sodass es dem OP nicht helfen würde.
Coxy

Das Problem war, dass der falsche Abfrageplan verwendet wurde. Ich würde es nicht auf das falsche einfrieren wollen.
Ian Boyd

4

Ich hatte dieses Problem. Meine Anfrage sah ungefähr so ​​aus:

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

Meine gespeicherte Prozedur wurde wie folgt definiert:

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

Ich habe den Datentyp in datetime und voila geändert! Ging von 30 Minuten bis 1 Minute!

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

2
Vielen Dank Lee, das hat mir den Tag gerettet! Hier ist, wie ich nur den Datumsteil eines Datums- / Uhrzeitfeldes erhalte: DATEADD (dd, 0, DATEDIFF (dd, 0, table.field))
Julien B.

1
Dies hat mein Problem behoben. Ich hatte die Spalte varchar (20) und mein Parameter war nvarchar (50), nachdem ich den Parametertyp mit dem Spaltentyp identisch gemacht hatte - keine Verzögerungen mehr.
st_stefanov

3

Haben Sie versucht, die Statistiken und / oder Indizes in der Tabelle Report_Opener neu zu erstellen? Alle Wiederholungen des SP sind nichts wert, wenn die Statistiken noch Daten aus der Zeit zeigen, als die Datenbank zum ersten Mal eröffnet wurde.

Die anfängliche Abfrage selbst funktioniert schnell, da der Optimierer erkennen kann, dass der Parameter niemals null sein wird. Im Fall des SP kann der Optimierer nicht sicher sein, dass der Parameter niemals null sein wird.


Gibt es eine Möglichkeit, in einer Deklaration einer gespeicherten Prozedur anzugeben, dass der Parameter i nicht null sein darf? Und ist es nicht etwas, das durch sp_executesql behoben werden würde?
Ian Boyd

Kurz gesagt, nicht im Jahr 2000. 2005 wurde ein Abfragehinweis hinzugefügt, in dem Sie einen Beispielwert für einen Parameter angeben können. Der Optimierer würde optimieren, als ob er wüsste, dass dieser Parameter immer verwendet wurde. Trotzdem habe ich festgestellt, dass solche Dinge im Allgemeinen ein Statistikproblem sind.
AnthonyWJones

Wenn es sich um ein Statistikproblem handelt, funktioniert es bei der Qualitätssicherung einwandfrei, wenn ich es ad-hoc, sp_executesql, exec () ausführe. Und warum laufen sie dann alle schlecht, wenn eine gespeicherte Prozedur das Ad-hoc-SQL sp_executesql, exec () enthält?
Ian Boyd

1

Obwohl ich normalerweise dagegen bin (obwohl es in diesem Fall so aussieht, als hätten Sie einen echten Grund), haben Sie versucht, Abfragehinweise zur SP-Version der Abfrage bereitzustellen? Wenn SQL Server in diesen beiden Fällen einen anderen Ausführungsplan vorbereitet, können Sie anhand eines Hinweises angeben, welcher Index verwendet werden soll, damit der Plan mit dem ersten übereinstimmt.

Einige Beispiele finden Sie hier .

BEARBEITEN: Wenn Sie Ihren Abfrageplan hier veröffentlichen können, können wir möglicherweise einen Unterschied zwischen den aussagekräftigen Plänen feststellen.

ZWEITENS: Der Link wurde so aktualisiert, dass er SQL-2000-spezifisch ist. Sie müssen ein Stück nach unten scrollen, aber es gibt eine zweite mit dem Titel "Table Hints", nach der Sie suchen.

DRITTES: Die "Bad" -Abfrage scheint die [IX_Openers_SessionGUID] in der "Openers" -Tabelle zu ignorieren. Gibt es eine Möglichkeit, einen INDEX-Hinweis hinzuzufügen, um die Verwendung dieses Index zu erzwingen?


Die nützlichsten Abfragehinweise in dieser Referenz sind in SQL 2000, der hier fraglichen Version, nicht verfügbar.
AnthonyWJones

Welche Hinweise werden zusätzlich benötigt? SQL Server ist in der Lage, es kein Problem herauszufinden, wenn ich es ad-hoc ausführe.
Ian Boyd

Klar, und das war schon immer auch meine Erfahrung. In diesem Fall sagt er jedoch, dass es einen völlig anderen Ausführungsplan gibt. Vielleicht gibt es einen Index, der ad-hoc verwendet wird, aber aus irgendeinem Grund im Prozess ignoriert wird. Er kann SQL Server zwingen, den Index mit dem Hinweis "INDEX" zu verwenden.
SqlRyan

1

Dies ist wahrscheinlich unwahrscheinlich, aber da Ihr beobachtetes Verhalten ungewöhnlich ist, muss es überprüft werden und niemand anderes hat es erwähnt.

Sind Sie absolut sicher, dass alle Objekte im Besitz von dbo sind und Sie keine betrügerischen Kopien von sich selbst oder einem anderen Benutzer haben?

Nur gelegentlich, wenn ich merkwürdiges Verhalten gesehen habe, liegt es daran, dass es tatsächlich zwei Kopien eines Objekts gab und welche Sie erhalten, hängt davon ab, was angegeben ist und als wen Sie angemeldet sind. Zum Beispiel ist es durchaus möglich, zwei Kopien einer Ansicht oder Prozedur mit demselben Namen zu haben, die sich jedoch im Besitz verschiedener Eigentümer befinden. Dies kann vorkommen, wenn Sie nicht als Datenbankbenutzer in der Datenbank angemeldet sind und vergessen, dbo als Objekteigentümer anzugeben, wenn Sie erstellen das Objekt.

Beachten Sie, dass Sie im Text einige Dinge ausführen, ohne den Eigentümer anzugeben, z

sp_recompile ViewOpener

Wenn beispielsweise zwei Kopien von viewOpener vorhanden sind, die dbo und [einem anderen Benutzer] gehören, hängt es von den Umständen ab, welche Sie tatsächlich neu kompilieren, wenn Sie nichts angeben. Das Gleiche gilt für die Ansicht "Report_Opener". Wenn zwei Kopien vorhanden sind (und sich in Spezifikation oder Ausführungsplan unterscheiden können), hängt die Verwendung von den Umständen ab. Da Sie keinen Eigentümer angeben, ist es durchaus möglich, dass Ihre Ad-hoc-Abfrage eine und die verwendet Die kompilierte Prozedur verwendet möglicherweise die andere.

Wie gesagt, es ist wahrscheinlich unwahrscheinlich, aber es ist möglich und sollte überprüft werden, da Ihre Probleme darin bestehen könnten, dass Sie einfach am falschen Ort nach dem Fehler suchen.


1

Dies mag albern klingen und scheint aus dem Namen SessionGUID ersichtlich zu sein, aber ist die Spalte eine eindeutige Kennung in Report_Opener? Wenn nicht, können Sie versuchen, es in den richtigen Typ umzuwandeln und es zu versuchen, oder Ihre Variable zum richtigen Typ deklarieren.

Der als Teil des Sproc erstellte Plan funktioniert möglicherweise nicht intuitiv und führt eine interne Besetzung auf einem großen Tisch durch.


Es ist nicht. Aber ich habe Leistungsprobleme mit einer where-Klausel gesehen, die eine varcharSpalte mit einem nvarcharWert (zB WHERE CustomerName = N'zrendall') verglichen hat . SQL Server musste nvarcharvor dem Vergleich jeden Spaltenwert in einen konvertieren .
Ian Boyd

0

Ich habe eine andere Idee. Was ist, wenn Sie diese tabellenbasierte Funktion erstellen:

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

Und dann mit der folgenden Anweisung daraus auswählen (sogar in Ihren SP einfügen):

SELECT *
FROM tbfSelectFromView(@SessionGUID)

Es sieht so aus, als ob SQL Server nur eine Annahme macht, die falsch ist, und dies zwingt ihn möglicherweise, die Annahme zu korrigieren. Ich hasse es, den zusätzlichen Schritt hinzuzufügen, bin mir aber nicht sicher, was ihn sonst noch verursachen könnte.


0

- Hier ist die Lösung:

create procedure GetOrderForCustomers(@CustID varchar(20))

as

begin

select * from orders

where customerid = ISNULL(@CustID, '')

end

-- Das ist es

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.