it-swarm.com.de

Optimieren der Verknüpfung auf einer großen Tabelle

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), hinzugefügt (in der where-Klausel verwendet) & id = (nutzlos) in aufsteigender Reihenfolge und enthält 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 hat hugetable einen Clustered-Index pk_hugetable (added, fk) (den Primärschlüssel) und einen nicht-Clustered-Index, der in die andere Richtung geht 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 angefordert, 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 Feld id ist redundant, ein Artefakt eines früheren DBA, der darauf bestand, dass alle Tabellen überall eine GUID haben sollten, keine Ausnahmen .

10
Quick Joe Smith

Ihr ix_hugetable Sieht ziemlich nutzlos aus, weil:

  • it is der Clustered Index (PK)
  • iNCLUDE macht keinen Unterschied, da ein Clustered-Index alle Nicht-Schlüsselspalten ENTHÄLT (Nicht-Schlüsselwerte 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 (added, fk, id) Zu ändern, und löschen Sie ix_hugetable. Sie haben 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 Hinweis FORCE ORDER 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 in INCLUDE der Wertespalte zu ändern, 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 semantisch COUNT(*). Aber für SUM braucht es den tatsächlichen Wert, nicht Existenz.

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

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.

5
gbn

Definieren Sie einen Index für hugetable nur für die Spalte added.

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

2
Bohemian

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 ist die richtige Wahl. 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, den Sie für die Verwendung im MERGE-Join erzwingen, ist so ziemlich 250 Millionen Zeilen * 'die Größe jeder Zeile' - nicht klein, zumindest mindestens ein paar GB . Nach dem sp_spaceused Die Ausgabe von 'ein paar GB' ist möglicherweise eine Untertreibung. Für den MERGE-Join müssen Sie den Index durchsuchen, der sehr E/A-intensiv sein wird.

2
Will A

Ihr Index ist falsch. Siehe indiziert 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 für hugetable(added, fk) hinzuzufügen. 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.

1