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.