Hinweis zur Kardinalität von SQL Server

14

Gibt es eine Möglichkeit, eine Kardinalitätsschätzung in ein SQL Server-Optimierungsprogramm (eine beliebige Version) einzufügen?

dh etwas Ähnliches wie der Kardinalitätshinweis von Oracle.

Meine Motivation beruht auf dem Artikel: Wie gut sind Abfrageoptimierer wirklich? [1] , wo sie den Einfluss des Kardinalitätsschätzers auf die Auswahl eines schlechten Plans testen. Daher würde es ausreichen, wenn ich den SQL Server zwingen könnte, die Kardinalitäten für komplexe Abfragen genau zu schätzen.


[1] Leis, Viktor et al. "Wie gut sind Abfrageoptimierer wirklich?"
Verfahren der VLDB-Stiftung 9.3 (2015): 204-215.

Radim Bača
quelle

Antworten:

10

Sie können einen ähnlichen CARDINALITYHinweis wie Oracle erhalten, indem Sie strategisch TOPeine benutzerdefinierte Funktion verwenden, MANY() die von Adam Machanic entwickelt wurde . Lassen Sie uns einige Beispiele durcharbeiten. Ich benutze die frei verfügbare AdventureWorks-Datenbank. Angenommen, ich muss die Anzahl der von der thabgeleiteten Tabelle zurückgegebenen Zeilen in der folgenden Abfrage wirklich steuern :

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
) th ON p.ProductID = th.ProductID;

Wie es ist, erhalte ich eine Schätzung von 113443 Zeilen:

Abfrage starten

Wenn ich die Schätzung von thverringern muss, kann ich TOPzusammen mit dem OPTIMIZE FORAbfragehinweis ein Zeilenziel festlegen. Hier ist eine Möglichkeit, dies zu tun:

DECLARE @row_goal BIGINT = 9223372036854775807;
SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1));

Wir können sehen, dass die Schätzung nur 1 Zeile ist:

Schätzung für 1 Zeile

Ich @row_goalhabe den größtmöglichen BIGINTWert gewählt, um die Ergebnisse nicht zu verändern. Der OPTIMIZE FORAbfragehinweis weist den Optimierer an, die Abfrage so zu optimieren, als ob sie @row_goalgleich 1 wäre. Ich erhalte dieselben Ergebnisse, die Abfrage wird jedoch unterschiedlich optimiert.

Das Erhöhen einer Kardinalitätsschätzung ist schwieriger. Wir können den Wert nicht einfach erhöhen, TOPda das Optimierungsprogramm feststellt, dass nicht genügend Zeilen zurückgegeben werden. Wir können die MANY()Funktion jedoch verwenden, um der Schätzung Zeilen hinzuzufügen. Beachten Sie, dass die MANY()Funktion immer 0 Zeilen zurückgibt, die daraus resultierende Zeilenschätzung sich jedoch mit dem Eingabeparameter ändert. Angenommen, Sie müssen die Zeilenschätzung aus der abgeleiteten Tabelle um das 10-Fache erhöhen. Ein Weg, dies zu erreichen:

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (9223372036854775807) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
    LEFT OUTER JOIN dbo.Many(10) AS m ON 1=1
) th ON p.ProductID = th.ProductID;

Wir können sehen, dass die Schätzung das 10fache der Basistabelle beträgt:

10X Abfrage

Das Überflüssige TOPwurde hinzugefügt, um zu verhindern, dass das Optimierungsprogramm die Tabellen verschiebt. Ohne diese kann die MANY()Funktion an der falschen Stelle im Plan angewendet werden.

Es ist möglich, die beiden Techniken zu kombinieren, wenn Sie eine präzise Überschätzung wünschen, anstatt nur die Anzahl der Zeilen mit einem Faktor zu multiplizieren. Angenommen, Sie müssen die Schätzung der abgeleiteten Tabelle wirklich auf genau 1000000 Zeilen festlegen. Ein Weg, dies zu erreichen:

DECLARE @row_goal BIGINT = 9223372036854775807;

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
    LEFT OUTER JOIN dbo.Many(10) AS m ON
        1=1
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1000000));

Wir können sehen, dass die Schätzung 1000000 Zeilen beträgt:

1 M Reihen

Ich muss Sie warnen, dass dies fortgeschrittene Techniken sind, die für die Abfrageoptimierung oft nicht benötigt werden. Wenn Sie mehr darüber erfahren möchten, empfehle ich Ihnen, sich die von Adam Machanic vorgestellten Clash of the Row Goals anzusehen.


dbo.Many Funktion

-- By Adam Machanic, reproduced with permission
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'Many' AND OBJECT_SCHEMA_NAME(object_id) = 'dbo')
    DROP FUNCTION dbo.Many
GO
CREATE FUNCTION dbo.Many(@n INT)
RETURNS TABLE AS
RETURN
(
    WITH
    a(x) AS
    (
        SELECT
            *
        FROM
        (
            VALUES
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)
        ) AS x0(x)
    )
    SELECT TOP(@n)
        1 AS x
    FROM
        a AS a1,
        a AS a2
    WHERE
        a1.x % 2 = 0
)
GO
Joe Obbish
quelle
9

Es gibt keine Möglichkeit, eine Kardinalitätsschätzung direkt in den Optimierer einzufügen, aber je nachdem, was Sie erreichen möchten, gibt es einige Optionen.

Sie könnten einen OPTION (FAST N)Abfragehinweis verwenden, um Zeilenziele einzuführen, und möglicherweise Ihre Abfrage mithilfe von CTEs oder Unterabfragen neu schreiben, um Zeilenziele TOP...ORDER BYin verschiedene Teile Ihres Ausführungsplans einzufügen , aber ich bin nicht sicher, wie effizient Ihre resultierende Abfrage sein wird, wenn Sie beginnen Herumspielen mit den komplexeren Konstruktionen.

Eine ausführlichere Erläuterung finden Sie unter Im Optimierer: Zeilenziele im Detail .

Wenn Sie die Operatoren beeinflussen wollen , dass der Optimierer nimmt Sie nicht brauchen , um zu versuchen , Kardinalität Schätzungen zu injizieren , aber man könnte Dinge wie verwenden OPTION (MERGE JOIN)oder OPTION (HASH JOIN)beispielsweise an die Betreiber beitreten physischer Gewalt.

In diesem Artikel wird ausführlicher beschrieben, wie Sie einen Plan mithilfe von Hinweisen beeinflussen können: Steuern von Ausführungsplänen mit Hinweisen

Wenn Sie einen Plan festlegen möchten, können Sie auch einen Planleitfaden verwenden.

Auch hier ist nicht klar, was Ihr tatsächlicher Anwendungsfall ist, und Ihre Laufleistung kann mit diesen Techniken variieren. In vielen Fällen ist es besser, nur den Optimierer entscheiden zu lassen und sicherzustellen, dass Sie über aktuelle Statistiken verfügen, damit der Optimierer eine fundierte Entscheidung treffen kann.


Relevanter Microsoft Connect-Vorschlag: Ermöglicht die Angabe eines Filterselektivitätshinweises in Abfragen von xor88. Microsoft antwortete:

Danke für die Rückmeldung. Ich sehe den möglichen Nutzen davon. Im Allgemeinen bemühen wir uns, unser automatisches Verhalten so gut wie möglich zu machen und die Notwendigkeit für diese Art von Hinweis zu vermeiden, aber wir haben natürlich viele andere Hinweise. Wir werden dies für eine zukünftige Version in Betracht ziehen, aber es würde über die Denali (11.0) -Version hinausgehen.

Mit freundlichen Grüßen
Eric Hanson
Programm-Manager
SQL Server-Abfrageverarbeitung

Tom V - Team Monica
quelle
3

Sie können den SQL Server- OPTIMIZE FORAbfragehinweis verwenden, um die Kardinalitätsschätzung basierend auf den angegebenen Werten zu erzwingen, anstatt den tatsächlichen Wert (Parameter) oder den unbekannten Wert (Variablen) während der Kompilierung zu verwenden. Siehe die Abfrage Hinweise Thema in der SQL Server - Dokumentation für weitere Informationen.

Die folgende Abfrage schätzt beispielsweise die Zeilenzahl basierend auf dem Statistikhistogramm aus den angegebenen Werten anstelle der durchschnittlichen Kardinalität, wie dies sonst bei lokalen Variablen der Fall wäre.

DECLARE 
      @StartDate datetime = '20150101'
    , @EndDate datetime = '20150102';
SELECT *
FROM dbo.Example
WHERE
    DateColumn BETWEEN  @StartDate AND @EndDate
OPTION(OPTIMIZE FOR(@StartDate = '20100101', @EndDate='20100101'));

In ähnlicher Weise kann der Hinweis für Parameter verwendet werden, so dass Schätzungen auf dem Statistikhistogramm aus angezeigten Werten anstelle der tatsächlichen Parameterwerte während der Kompilierung basieren.

DECLARE 
      @StartDate datetime = '20150101'
    , @EndDate datetime = '20150102';
EXECUTE sp_executesql N'SELECT *
        FROM dbo.Example
        WHERE
            DateColumn BETWEEN  @StartDate AND @EndDate
        OPTION(OPTIMIZE FOR(@StartDate = ''20100101'', @EndDate=''20100101''));'
    , N'@StartDate datetime, @EndDate datetime'
    , @StartDate = @StartDate
    , @EndDate = @EndDate;

Das UNKNOWNSchlüsselwort kann anstelle eines Literalen im Hinweis angegeben werden, um die durchschnittliche Kardinalität insgesamt zu verwenden, anstatt basierend auf dem tatsächlichen Parameterwert und dem Statistikhistogramm zu schätzen.

Dan Guzman
quelle