it-swarm.com.de

Hinweis zur Kardinalität von SQL Server

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

etwas Ähnliches wie der Kardinalitätshinweis von Oracle.

Meine Motivation basiert 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äre es ausreichend, wenn ich den SQL Server zwingen könnte, die Kardinalitäten genau für komplexe Abfragen zu "schätzen".


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

14
Radim Bača

Sie können etwas Ähnliches wie den Hinweis von Oracle CARDINALITY erhalten, indem Sie strategisch TOP und eine benutzerdefinierte Funktion namens MANY()entwickelt von Adam Machanic verwenden. Lassen Sie uns einige Beispiele durcharbeiten. Ich verwende die frei verfügbare AdventureWorks-Datenbank. Angenommen, ich muss wirklich die Anzahl der Zeilen steuern, die von der abgeleiteten Tabelle th in der folgenden Abfrage zurückgegeben werden:

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:

(starting query

Wenn ich die Schätzung von th senken muss, kann ich TOP zusammen mit dem Abfragehinweis OPTIMIZE FOR Verwenden, um ein Zeilenziel festzulegen. 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 beträgt:

(1 row estimate

Ich habe @row_goal Auf den größtmöglichen Wert BIGINT gesetzt, um eine Änderung der Ergebnisse zu vermeiden. Der Abfragehinweis OPTIMIZE FOR Weist den Optimierer an, die Abfrage so zu optimieren, als ob @row_goal Gleich 1 ist. Ich erhalte die gleichen Ergebnisse, aber die Abfrage wird anders optimiert.

Das Erhöhen einer Kardinalitätsschätzung ist schwieriger. Wir können den Wert für TOP nicht einfach erhöhen, da der Optimierer erkennt, dass nicht genügend Zeilen zurückgegeben werden. Wir können jedoch die Funktion MANY() verwenden, um der Schätzung Zeilen hinzuzufügen. Beachten Sie, dass die Funktion MANY() immer 0 Zeilen zurückgibt, die daraus abgeleitete 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 10-fache der Basistabelle beträgt:

(10X query

Das überflüssige TOP wurde hinzugefügt, um zu verhindern, dass der Optimierer die Tabellen verschiebt. Ohne sie kann die Funktion MANY() an der falschen Stelle im Plan angewendet werden.

Es ist möglich, die beiden Techniken zu kombinieren, wenn Sie eine genaue Überschätzung wünschen, anstatt nur die Anzahl der Zeilen mit einem Faktor zu multiplizieren. Angenommen, Sie benötigen eine Schätzung der abgeleiteten Tabelle von genau 1000000 Zeilen. 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 rows

Ich muss Sie darauf hinweisen, dass dies fortgeschrittene Techniken sind, die für die Abfrageoptimierung häufig nicht benötigt werden. Wenn Sie mehr erfahren möchten, empfehle ich Ihnen, Clash of the Row Goals von Adam Machanic zu sehen.


dbo.Viele 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
11
Joe Obbish

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 Abfragehinweis OPTION (FAST N) verwenden, um Zeilenziele einzuführen, und möglicherweise Ihre Abfrage mithilfe von CTEs oder Unterabfragen neu schreiben, um TOP...ORDER BY - basierte Zeilenziele in verschiedene Teile Ihres Ausführungsplans einzufügen, aber ich ' Ich bin mir nicht sicher, wie effizient Ihre resultierende Abfrage sein wird, wenn Sie anfangen, mit den komplexeren Konstruktionen herumzuspielen.

Eine ausführlichere Erklärung finden Sie unter Im Optimierer: Zeilenziele im Detail .

Wenn Sie die vom Optimierer ausgewählten Operatoren beeinflussen möchten, müssen Sie nicht versuchen, Kardinalitätsschätzungen einzufügen, sondern können beispielsweise OPTION (MERGE JOIN) oder OPTION (HASH JOIN) verwenden, um physische Verknüpfungsoperatoren zu erzwingen .

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

Wenn Sie einen Plan reparieren möchten, können Sie auch eine Plananleitung verwenden.

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


Relevanter Microsoft Connect-Vorschlag: Geben Sie in Abfragen einen Hinweis zur Filterselektivität an. by xor88. Microsoft antwortete:

Danke für die Rückmeldung. Ich kann den potenziellen Nutzen davon sehen. Im Allgemeinen bemühen wir uns, unser automatisches Verhalten so gut wie möglich zu gestalten und die Notwendigkeit dieser Art von Hinweis zu vermeiden, aber natürlich haben wir 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.

Freundliche Grüße,
Eric Hanson
Progamm Manager
SQL Server-Abfrageverarbeitung

Sie können den SQL Server-Abfragehinweis OPTIMIZE FOR 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. Ausführliche Informationen finden Sie im Thema Abfragehinweise in der SQL Server-Dokumentation.

In der folgenden Abfrage werden beispielsweise die Zeilenzahlen basierend auf dem Statistikhistogramm anhand der angegebenen Werte anstelle der durchschnittlichen Gesamtkardinalität geschätzt, 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, sodass Schätzungen auf dem Statistikhistogramm von angedeuteten Werten anstelle von tatsächlichen Parameterwerten 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 Schlüsselwort UNKNOWN kann anstelle eines Literals im Hinweis angegeben werden, um die durchschnittliche Gesamtkardinalität zu verwenden, anstatt basierend auf dem tatsächlichen Parameterwert und dem Statistikhistogramm zu schätzen.

3
Dan Guzman