Optimieren des Joins auf einer großen Tabelle


10

Ich versuche, einer Abfrage, die auf eine Tabelle mit ~ 250 Millionen Datensätzen zugreift, mehr Leistung zu entlocken. Nach meiner Lektüre des tatsächlichen (nicht geschätzten) Ausführungsplans ist der erste Engpass eine Abfrage, die folgendermaßen aussieht:

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
where
    a.added between @start and @end;

Weiter unten finden Sie die Definitionen der beteiligten Tabellen und Indizes.

Der Ausführungsplan gibt an, dass für #smalltable eine verschachtelte Schleife verwendet wird und dass der Index-Scan über hugetable 480 Mal ausgeführt wird (für jede Zeile in #smalltable). Dies scheint mir rückwärts zu sein, daher habe ich versucht, stattdessen die Verwendung eines Merge-Joins zu erzwingen:

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a with(index = ix_hugetable)
    inner merge join
    #smalltable b with(index(1)) on a.fk = b.pk
where
    a.added between @start and @end;

Der betreffende Index (vollständige Definition siehe unten) umfasst die Spalten fk (das Join-Prädikat), die in aufsteigender Reihenfolge hinzugefügt (in der where-Klausel verwendet) und id (nutzlos) und enthält den Wert .

Wenn ich dies jedoch tue, wird die Abfrage von 2 1/2 Minuten auf über 9 Minuten ausgeblasen. Ich hätte gehofft, dass die Hinweise einen effizienteren Join erzwingen würden, der nur einen einzigen Durchgang über jede Tabelle ausführt, aber eindeutig nicht.

Jede Anleitung ist willkommen. Zusätzliche Informationen bei Bedarf.

Update (02.06.2011)

Nachdem ich die Indizierung für die Tabelle neu organisiert habe, habe ich erhebliche Leistungssteigerungen erzielt, bin jedoch auf ein neues Hindernis gestoßen, wenn es darum geht, die Daten in der riesigen Tabelle zusammenzufassen. Das Ergebnis ist eine monatliche Zusammenfassung, die derzeit wie folgt aussieht:

select
    b.stuff,
    datediff(month, 0, a.added),
    count(a.value),
    sum(case when a.value > 0 else 1 end) -- this triples the running time!
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
group by
    b.stuff,
    datediff(month, 0, a.added);

Derzeit verfügt hugetable über einen Clustered-Index pk_hugetable (added, fk)(den Primärschlüssel) und einen nicht-Clustered-Index in die andere Richtung ix_hugetable (fk, added).

Ohne die vierte Spalte oben verwendet der Optimierer wie zuvor einen verschachtelten Schleifen-Join, wobei #smalltable als äußere Eingabe verwendet wird, und eine nicht gruppierte Indexsuche als innere Schleife (erneut 480-mal ausgeführt). Was mich betrifft, ist die Ungleichheit zwischen den geschätzten Zeilen (12.958,4) und den tatsächlichen Zeilen (74.668.468). Die relativen Kosten dieser Suchanfragen betragen 45%. Die Laufzeit beträgt jedoch weniger als eine Minute.

Mit der 4. Spalte erhöht sich die Laufzeit auf 4 Minuten. Diesmal sucht es im Clustered-Index (2 Ausführungen) nach den gleichen relativen Kosten (45%), aggregiert über eine Hash-Übereinstimmung (30%) und führt dann einen Hash-Join für #smalltable (0%) durch.

Ich bin mir nicht sicher, wie ich als nächstes vorgehen soll. Ich mache mir Sorgen, dass weder die Datumsbereichssuche noch das Join-Prädikat garantiert sind oder sogar alles, was die Ergebnismenge drastisch reduzieren könnte. Der Datumsbereich schneidet in den meisten Fällen nur 10-15% der Datensätze ab, und der innere Join auf fk filtert möglicherweise 20-30% heraus.


Wie von Will A gefordert, sind die Ergebnisse von sp_spaceused:

name      | rows      | reserved    | data        | index_size  | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB

#smalltable ist definiert als:

create table #endpoints (
    pk uniqueidentifier primary key clustered,
    stuff varchar(6) null
);

Während dbo.hugetable definiert ist als:

create table dbo.hugetable (
    id uniqueidentifier not null,
    fk uniqueidentifier not null,
    added datetime not null,
    value decimal(13, 3) not null,

    constraint pk_hugetable primary key clustered (
        fk asc,
        added asc,
        id asc
    )
    with (
        pad_index = off, statistics_norecompute = off,
        ignore_dup_key = off, allow_row_locks = on,
        allow_page_locks = on
    )
    on [primary]
)
on [primary];

Mit folgendem Index definiert:

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc, id asc
) include(value) with (
    pad_index = off, statistics_norecompute = off,
    sort_in_tempdb = off, ignore_dup_key = off,
    drop_existing = off, online = off,
    allow_row_locks = on, allow_page_locks = on
)
on [primary];

Das ID- Feld ist redundant, ein Artefakt eines früheren DBA, der darauf bestand, dass alle Tabellen überall eine GUID haben sollten, keine Ausnahmen.


Könnten Sie bitte das Ergebnis von sp_spaceused 'dbo.hugetable' angeben?
Will A

Fertig, direkt über dem Anfang der Tabellendefinitionen hinzugefügt.
Quick Joe Smith

Es ist sicher. Seine lächerliche Größe ist der Grund, warum ich mich damit beschäftige.
Quick Joe Smith

Antworten:


5

Du ix_hugetablesiehst ziemlich nutzlos aus, weil:

  • Es ist der Clustered Index (PK).
  • INCLUDE macht keinen Unterschied, da ein Clustered-Index alle Nicht-Schlüsselspalten ENTHÄLT (Nicht-Key-Werte am niedrigsten Blatt = INCLUDEd = was ein Clustered-Index ist)

Zusätzlich: - hinzugefügt oder fk sollte zuerst sein - ID ist zuerst = nicht viel Verwendung

Versuchen Sie, den Clustered-Schlüssel in zu ändern (added, fk, id)und zu löschen ix_hugetable. Du hast es bereits versucht (fk, added, id). Wenn nichts anderes, sparen Sie viel Speicherplatz und Indexpflege

Eine andere Möglichkeit könnte darin bestehen, den FORCE ORDER-Hinweis mit Tabellenreihenfolge und ohne JOIN / INDEX-Hinweise auszuprobieren. Ich versuche, JOIN / INDEX-Hinweise nicht persönlich zu verwenden, da Sie Optionen für den Optimierer entfernen. Vor vielen Jahren wurde mir gesagt (Seminar mit einem SQL Guru), dass der FORCE ORDER-Hinweis helfen kann, wenn Sie einen großen Tisch haben. JOIN small table: YMMV 7 Jahre später ...

Oh, und lassen Sie uns wissen, wo der DBA lebt, damit wir eine Percussion-Anpassung arrangieren können

Bearbeiten, nach dem Update vom 02. Juni

Die 4. Spalte ist nicht Teil des nicht gruppierten Index, daher wird der gruppierte Index verwendet.

Versuchen Sie, den NC-Index so zu ändern, dass die Wertespalte eingeschlossen wird, damit nicht auf die Wertespalte für den Clustered-Index zugegriffen werden muss

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc
) include(value)

Hinweis: Wenn der Wert nicht nullwertfähig ist, entspricht er dem COUNT(*)semantischen Wert . Aber für SUM braucht es den tatsächlichen Wert, nicht die Existenz .

Wenn Sie beispielsweise zu wechseln COUNT(value), COUNT(DISTINCT value) ohne den Index zu ändern, sollte die Abfrage erneut unterbrochen werden, da der Wert als Wert und nicht als Existenz verarbeitet werden muss.

Die Abfrage benötigt 3 Spalten: addiert, fk, Wert. Die ersten 2 werden gefiltert / verbunden, ebenso die Schlüsselspalten. Wert wird nur verwendet, kann also eingeschlossen werden. Klassische Verwendung eines Deckungsindex.


Hah, ich hatte es im Kopf, dass die gruppierten und nicht gruppierten Indizes fk & in unterschiedlicher Reihenfolge hinzugefügt hatten. Ich kann nicht glauben, dass ich das nicht bemerkt habe, fast so sehr, wie ich nicht glauben kann, dass es überhaupt so eingerichtet wurde. Ich werde morgen den Clustered-Index ändern und dann die Straße entlang gehen, um einen Kaffee zu trinken, während er wieder aufgebaut wird.
Quick Joe Smith

Ich habe die Indizierung geändert und eine Bash mit FORCE ORDER durchgeführt, um die Anzahl der Suchvorgänge auf dem großen Tisch zu reduzieren, aber ohne Erfolg. Meine Frage wurde aktualisiert.
Schnell Joe Smith

@ Quick Joe Smith: aktualisiert meine Antwort
gbn

Ja, das habe ich nicht lange danach versucht. Weil die Indexwiederherstellung so lange dauert, habe ich es vergessen und anfangs gedacht, ich hätte es beschleunigt, etwas völlig anderes zu tun.
Quick Joe Smith

2

Definieren Sie einen Index hugetablenur für die addedSpalte.

DBs verwenden einen mehrteiligen (mehrspaltigen) Index nur ganz rechts von der Spaltenliste, da die Werte von links zählen. Ihre Abfrage wird fkin der where-Klausel der ersten Abfrage nicht angegeben , daher wird der Index ignoriert.


Der Ausführungsplan zeigt , dass der Index (ix_hugetable) wird seeked wird. Oder sagen Sie, dass dieser Index für die Abfrage nicht geeignet ist?
Schnell Joe Smith

Der Index ist nicht angemessen. Wer weiß, wie es "mit dem Index" ist. Die Erfahrung zeigt mir, dass dies Ihr Problem ist. Probieren Sie es aus und sagen Sie uns, wie es geht.
Böhmischer

@ Quick Joe Smith - hast du den Vorschlag von @ Bohemian ausprobiert? Was wo die Ergebnisse?
Lieven Keersmaekers

2
Ich bin anderer Meinung: Die ON-Klausel wird zuerst logisch verarbeitet und ist in der Praxis praktisch ein WHERE, sodass OP zuerst beide Spalten ausprobieren muss. Überhaupt keine Indizierung für fk = Clustered-Index-Scan oder Schlüsselsuche, um den fk-Wert für JOIN zu erhalten. Können Sie bitte auch einige Verweise auf das von Ihnen beschriebene Verhalten hinzufügen? Insbesondere für SQL Server haben Sie nur wenig Vorgeschichte, um auf dieses RDBMS zu antworten. Eigentlich -1 im Nachhinein als aI diesen Kommentar
eingeben

2

Der Ausführungsplan gibt an, dass für #smalltable eine verschachtelte Schleife verwendet wird und dass der Index-Scan über hugetable 480 Mal ausgeführt wird (für jede Zeile in #smalltable).

Dies ist die Reihenfolge, die das Abfrageoptimierungsprogramm voraussichtlich verwenden wird, vorausgesetzt, ein Loop-Join hat die richtige Auswahl. Die Alternative besteht darin, 250 Millionen Mal eine Schleife durchzuführen und jedes Mal eine Suche in der # temp-Tabelle durchzuführen - was durchaus Stunden / Tage dauern kann.

Der Index Sie zwingen, in der MERGE verwendet werden , kommen ist ziemlich 250M Reihen * ‚die Größe jeder Zeile‘ - nicht klein, zumindest ein paar GB. Nach der sp_spaceusedAusgabe "ein paar GB" zu urteilen , könnte eine ziemliche Untertreibung sein - der MERGE-Join erfordert, dass Sie den Index durchsuchen, der sehr E / A-intensiv sein wird.


Nach meinem Verständnis gibt es drei Arten von Join-Algorithmen, und der Merge-Join weist die beste Leistung auf, wenn beide Eingaben nach dem Join-Prädikat geordnet sind. Zu Recht oder zu Unrecht ist dies das Ergebnis, das ich erreichen möchte.
Schnell Joe Smith

2
Aber es steckt noch mehr dahinter. Wenn #smalltable eine große Anzahl von Zeilen hat, ist möglicherweise ein Zusammenführungs-Join geeignet. Wenn es, wie der Name schon sagt, eine kleine Anzahl von Zeilen hat, könnte ein Loop-Join die richtige Wahl sein. Stellen Sie sich vor, #smalltable hätte eine oder zwei Zeilen und würde mit einer Handvoll Zeilen aus der anderen Tabelle übereinstimmen - es wäre schwierig, einen Zusammenführungsbeitrag hier zu rechtfertigen.
Will A

Ich nahm an, dass mehr dahinter steckt. Ich wusste einfach nicht, was das sein könnte. Die Datenbankoptimierung ist nicht gerade meine Stärke, wie Sie wahrscheinlich bereits erraten haben.
Schnell Joe Smith

@ Quick Joe Smith - danke für den sp_spaceused. 75 GB Index und 18 GB Daten - ist ix_hugetable nicht der einzige Index in der Tabelle?
Will A

1
+1 Wille. Der Planer macht gerade das Richtige. Das Problem liegt in zufälligen Festplattensuchen aufgrund der Art und Weise, wie Ihre Tabellen geclustert sind.
Denis de Bernardy

1

Ihr Index ist falsch. Siehe Indexe Dos und Donts .

Aus heutiger Sicht ist Ihr einziger nützlicher Index der auf dem Primärschlüssel der kleinen Tabelle. Der einzig vernünftige Plan ist es daher, den kleinen Tisch zu scannen und das Chaos mit dem großen zu verschachteln.

Versuchen Sie, einen Clustered-Index hinzuzufügen hugetable(added, fk). Dies sollte den Planer dazu bringen, geeignete Zeilen aus der großen Tabelle zu suchen und sie mit der kleinen Tabelle zu verschachteln oder zusammenzuführen.


Danke für diesen Link. Ich werde es versuchen, wenn ich morgen zur Arbeit komme.
Quick Joe Smith
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.