it-swarm.com.de

Der Ausführungsplan für Abfragen ist schrecklich, bis die Statistiken aktualisiert werden

Ich hoffe ihr könnt mir hier helfen. Unsere Anwendung fragt alle 3 Sekunden eine Nachrichtentabelle nach Benachrichtigungen ab, die gesendet werden sollen. Dies funktioniert gut bei allen unseren Kunden (Single-Tenant-DB) mit einer Ausnahme. Sie haben 23 Stunden am Tag keine Aktivität und laden dann Tausende von Nachrichten gleichzeitig (3000+). In anderen Fällen ist dieses Volume nichts und wir können problemlos damit umgehen, außer in diesem Fall dauert die Ausführung der folgenden SQL-Abfrage ungefähr 30 Sekunden und wird schlimmer, wenn die Warteschlange während eines Updates gesichert wird, eine exklusive Sperre erfordert und Das Blockieren aller anderen Abfragen und damit der Probleme verursacht alle Arten von Chaos. Dies ist alles auf einen schlechten Abfrageplan zurückzuführen.

Wir haben eine tägliche Neuindizierung, die jeden Morgen um 5 Uhr morgens ausgeführt wird (<30% neu organisieren,> 30% neu erstellen, <5% ignorieren) sowie Statistiken aktualisieren. Diese stammen beide aus der Ola Hallengren Wartungslösung. Wir sind auch auf SQL Server 2016 und sind auf dem neuesten Stand (13.0.5492.2)

Ich habe die 2 Pläne nicht zur Hand, aber im Grunde geht der schlechte Plan und führt einen vollständigen Tabellenscan der MessagesSent-Tabelle durch (3,5 m Zeilen).

Meine Theorie ist, dass bestimmte Teile nicht ausgeführt werden, da die Abfrage den ganzen Tag nichts zurückgibt. Daher ist die fehlerhafte Abfrage die effizienteste Abfrage für SQL.

Dies wird fortgesetzt, nachdem der Plan für die Abfrage geleert wurde, da nur derselbe Plan generiert wird. Wenn ich jedoch STATISTIKEN in der MessagesSent-Tabelle AKTUALISIERE, wird der gute Plan erstellt und alles ist fehlerfrei. Die Abfrage wird in etwa 10 bis 30 ms ausgeführt.

Weiß jemand, wie ich dies optimieren kann, um immer den besseren Plan zu verwenden, auch wenn keine Daten für die Rückgabe der Abfrage vorhanden sind? Als Hotfix haben wir der Anwendung eine Option zum erneuten Kompilieren hinzugefügt, aber ich glaube nicht, dass dies die ideale Lösung für eine Abfrage ist, die alle 3 Sekunden ausgeführt wird.

Hier ist die Abfrage:

WITH TopMessage
    AS
    (
        SELECT TOP 1 ID, BatchID FROM MessagesSent 
        JOIN Units ON Unit = idUnit 
        WHERE   MessageDate <= GETDATE() 
          AND         Active = 'True' 
          AND         Status = 'Queued' 
          AND NOT(DialString = 'null') 
          AND           Unit = ('29') 
          AND System in ('SystemName', 'Q1', '') 
        ORDER BY 
            CASE 
                WHEN QPriority IS NULL 
                    THEN 
                        CASE 
                            WHEN DefaultPriority IS NULL 
                                THEN 999999 
                                ELSE DefaultPriority 
                        END
                 ELSE QPriority 
            END ASC,
            Retries ASC, 
            MessageDate ASC, 
            ID ASC
    ),
    BatchCalls
    AS
    (
        SELECT * FROM MessagesSent 
        WHERE (
                 (LEN(BatchID) > 0 
                  AND BatchID = (SELECT TOP 1 BatchID FROM TopMessage)
                 ) 
        OR ID = (SELECT TOP 1 ID FROM TopMessage)
        )
        AND Status = 'Queued' AND Active = 'True'
    )

    UPDATE BatchCalls
    SET LastUpdated = @dtNow
    OUTPUT INSERTED.*
    WHERE Status = 'Queued'

Vielen Dank für Ihre Zeit und für das Schauen.

4
WadeH

Sie können einen manuellen Planleitfaden verwenden, um den gewünschten Plan durchzusetzen.

Alternativ können Sie den Abfragespeicher verwenden, um Planhandbücher über die GUI zu erzwingen.

Sie können auch einen Aktualisierungsstatistikjob nach der großen Anzahl von Nachrichten ausführen. Ich habe einen Beitrag in meinem Blog geschrieben, der einen einfachen Weg zeigt, dies zu tun .

9
Max Vernon

Da diese Abfragen häufig gefiltert werden, können Sie möglicherweise die Arbeit beschleunigen und das Sperren/Blockieren reduzieren, indem Sie einen gefilterten Index für die Spalten "Aktiv" und "Status" hinzufügen.

Es ist schwierig zu sagen, welche Spalten zu welchen Tabellen ohne das Schema gehören, aber der Index würde ungefähr so ​​aussehen:

CREATE NONCLUSTERED INDEX IX_BatchID_Filtered
ON dbo.MessagesSent (BatchID, Active, Status)
WHERE Active = 'True' AND Status = 'Queued';

Hinweis: Möglicherweise gibt es eine bessere führende Spalte als BatchID, und Sie möchten möglicherweise andere Spalten aus der Tabelle hinzufügen (z. B. DialString, System, QPriority usw.). - Ich war einfach nicht der Meinung, welche Spalten zu welchen Tabellen gehörten. und was ihre Datentypen sind, etc.

Dieser Index würde nur die (hoffentlich kleine) Teilmenge von Zeilen enthalten, die diese Kriterien erfüllen, und eine etwas vorhersehbarere Leistung angesichts etwas veralteter Statistiken bieten.

6
Josh Darnell