Verwenden der $ Partition-Funktion zur Verbesserung der Abfrageleistung

7

Ich habe Tabellen, die basierend auf einer INTSpalte partitioniert sind .

Ich sehe einige Abfragen, die $PartitionFunktionen verwenden, um die Partitionsnummer zu vergleichen, anstatt die tatsächlichen Felddaten zu vergleichen.

Zum Beispiel anstatt zu sagen:

select *
from T1 
    inner join T2 on T2.SnapshotKey = T1.SnapshotKey

Sie wurden wie folgt geschrieben:

select *
from T1 
    inner join T2 on $Partition.PF_Name(T2.SnapshotKey) = $Partition.PF_Name(T1.SnapshotKey)

Wo PF_Nameist der Name der Partitionsfunktion?

Ich sehe Kommentare zu diesen Abfragen, die zur Verbesserung der Leistung durchgeführt wurden, und wenn ich beide Abfragen ausführe, sehe ich unterschiedliche Ausführungszeiten und unterschiedliche Ausführungspläne. Ich bin mir nicht sicher, wie sich diese beiden Abfragen unterscheiden.

Dies ist eine echte Frage:

-- this takes about 9 seconds 
select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703

-- this takes about 5 seconds
select sum(PrinBal)
from fpc
    inner join gl on $Partition.SnapshotKeyPF(gl.SnapshotKey) = $Partition.SnapshotKeyPF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703

Und unten ist der Ausführungsplan der realen Abfrage:

Den Ausführungsplan sehen Sie hier

Leider kann ich nicht einmal einen bereinigten Ausführungsplan hochladen, da Uploads von unserem Netzwerk überwacht werden und möglicherweise ein Richtlinienverstoß vorliegt.

Die Frage ist: Warum Ausführungspläne unterschiedlich sind und warum die zweite Abfrage schneller ist.

Ich freue mich, wenn jemand eine Idee dazu teilen kann. Nur interessiert zu wissen, warum sie unterschiedlich sind. Schneller ist aber besser.

Dies geschieht unter SQL Server 2014. Wenn ich unter SQL Server 2012 dasselbe ausführe, ist das Ergebnis anders und die erste Abfrage wird schneller ausgeführt!

Gemeinschaft
quelle

Antworten:

9

Warum Ausführungspläne unterschiedlich sind

Erste Abfrage

select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703

Der Optimierer weiß:

  • gl.SnapshotKey = fpc.SnapshotKey;; und
  • fpc.SnapshotKey = 201703

so kann es schließen:

  • gl.SnapshotKey = 201703

Als ob Sie geschrieben hätten:

select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703
and gl.SnapshotKey = 201703

Der Literalwert 201703 kann auch vom Optimierer verwendet werden, um die Partitions-ID zu bestimmen. Bei beiden SnapshotKeyPrädikaten (eines angegeben, eines abgeleitet) bedeutet dies, dass der Optimierer die Partitions-ID für beide Tabellen kennt.

Mit einem Literalwert (201703) SnapshotKey, der derzeit in beiden Tabellen verfügbar ist, wird das Join-Prädikat weiter ausgeführt:

  • gl.SnapshotKey = fpc.SnapshotKey

vereinfacht zu:

  • 201703 = 201703;; oder einfach
  • true

Das heißt, es gibt überhaupt kein Join-Prädikat. Das Ergebnis ist eine logische Querverbindung. Wenn Sie den endgültigen Ausführungsplan mit der nächstgelegenen verfügbaren T-SQL-Syntax ausdrücken, ist es so, als hätten Sie geschrieben:

SELECT
    CASE
        WHEN SUM(Q1.c) = 0 THEN NULL
        ELSE SUM(Q1.s)
    END
FROM 
(
    SELECT c = COUNT_BIG(*), s = SUM(GL.PrinBal)
    FROM dbo.gl AS GL
    WHERE GL.SnapshotKey = 201703
    AND $PARTITION.PF(GL.SnapshotKey) = $PARTITION.PF(201703)
) AS Q1
CROSS JOIN
(
    SELECT Dummy = 1
    FROM dbo.fpc AS FPC
    WHERE FPC.SnapshotKey = 201703
    AND $PARTITION.PF(FPC.SnapshotKey) = $PARTITION.PF(201703)
) AS Q2;

Zweite Abfrage

select sum(PrinBal)
from fpc
    inner join gl on $Partition.PF(gl.SnapshotKey) = $Partition.PF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703

Das Optimierungsprogramm kann nichts mehr ableiten gl.SnapshotKey, sodass die für die erste Abfrage vorgenommenen Vereinfachungen und Transformationen nicht mehr möglich sind.

Wenn nicht wahr ist, dass jede Partition nur eine einzige enthält SnapshotKey, kann beim Umschreiben nicht garantiert werden, dass dieselben Ergebnisse erzielt werden.

Drücken Sie erneut den Ausführungsplan aus, der mit der nächstgelegenen verfügbaren T-SQL-Syntax erstellt wurde:

SELECT
    CASE
        WHEN SUM(Q2.c) = 0 THEN NULL
        ELSE SUM(Q2.s)
    END
FROM 
(
    SELECT
        Q1.PtnID,
        c = COUNT_BIG(*),
        s = SUM(Q1.PrinBal)
    FROM 
    (
        SELECT GL.PrinBal, PtnID = $PARTITION.PF(GL.SnapshotKey)
        FROM dbo.gl AS GL
    ) AS Q1
    GROUP BY
        Q1.PtnID
) AS Q2
CROSS APPLY
(
    SELECT Dummy = 1
    FROM dbo.fpc AS FPC
    WHERE
        $PARTITION.PF(FPC.SnapshotKey) = Q2.PtnID
        AND FPC.SnapshotKey = 201703
) AS Q3;

Diesmal gibt es keine logische Querverbindung. Stattdessen gibt es einen korrelierten Join (ein Apply) für die Partitions-ID.


warum die zweite Abfrage schneller ist.

Dies ist anhand der gegebenen Informationen schwer zu beurteilen. Bei Verwendung von Scheindaten und Tabellen basierend auf den bereitgestellten Abfragen und dem Planbild stellte ich fest, dass die erste Abfrage in jedem Fall die zweite übertraf.

Dieselbe Abfrage, die mit unterschiedlicher Syntax ausgedrückt wird, kann häufig zu einem anderen Ausführungsplan führen, einfach weil das Optimierungsprogramm an einem anderen Punkt gestartet und Optionen in einer anderen Reihenfolge untersucht hat, bevor es einen geeigneten Ausführungsplan gefunden hat. Die Plansuche ist nicht erschöpfend und nicht jede mögliche logische Transformation ist verfügbar. Daher ist das Endergebnis wahrscheinlich unterschiedlich. Wie oben erwähnt, drücken die beiden Abfragen ohnehin nicht unbedingt dieselbe Anforderung aus (zumindest angesichts der Informationen, die dem Optimierer zur Verfügung stehen).

Beachten Sie in einem separaten Hinweis, dass die anfängliche Implementierung des Spaltenspeichers in SQL Server 2012 (und in etwas geringerem Umfang 2014) viele Einschränkungen aufweist, nicht zuletzt auf der Optimierungsseite. Sie werden wahrscheinlich bessere und konsistentere Ergebnisse erzielen, wenn Sie auf eine neuere Version (im Idealfall die neueste) aktualisieren. Dies gilt insbesondere dann, wenn Sie die Partitionierung verwenden möchten.

Ich würde Ihnen sicherlich nicht empfehlen, sich daran zu gewöhnen, Joins mit neu zu schreiben $PARTITION, außer als allerletzter Ausweg und mit einem sehr tiefen Verständnis dafür, was Sie tun.

Das ist ungefähr alles, was ich sagen kann, ohne das Schema oder die Planungsdetails sehen zu können.

Paul White 9
quelle