Sto cercando di convincere un po 'di più le prestazioni di una query che accede a una tabella con circa 250 milioni di record. Dalla mia lettura del piano di esecuzione effettivo (non stimato), il primo collo di bottiglia è una query simile a questa:
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;
Vedi più in basso per le definizioni delle tabelle e degli indici coinvolti.
Il piano di esecuzione indica che un ciclo nidificato viene utilizzato su #smalltable e che la scansione dell'indice su hugetable viene eseguita 480 volte (per ogni riga in #smalltable). Questo mi sembra arretrato, quindi ho cercato di forzare invece un merge join da utilizzare:
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;
L'indice in questione (vedi sotto per la definizione completa) copre le colonne fk (predicato del join), aggiunte (usate nella clausola where) e id (inutili) in ordine crescente e include valore .
Quando lo faccio, tuttavia, la query esplode da 2 1/2 minuti a oltre 9. Avrei sperato che i suggerimenti avrebbero forzato un join più efficiente che fa solo un singolo passaggio su ogni tabella, ma chiaramente no.
Qualsiasi consiglio è il benvenuto. Ulteriori informazioni fornite se necessario.
Aggiornamento (2011/06/02)
Dopo aver riorganizzato l'indicizzazione sul tavolo, ho fatto progressi significativi in termini di prestazioni, tuttavia ho colto un nuovo ostacolo quando si tratta di riassumere i dati nella tabella enorme. Il risultato è un riepilogo per mese, che attualmente appare come il seguente:
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);
Al momento, hugetable ha un indice cluster pk_hugetable (added, fk)
(la chiave primaria) e un indice non cluster che va nell'altra direzione ix_hugetable (fk, added)
.
Senza la quarta colonna sopra, l'ottimizzatore utilizza un join di loop nidificato come prima, utilizzando #smalltable come input esterno e un indice non cluster cerca come loop interno (eseguendo nuovamente 480 volte). Ciò che mi preoccupa è la disparità tra le file stimate (12.958,4) e quelle effettive (74.668.468). Il costo relativo di queste ricerche è del 45%. Il tempo di esecuzione è tuttavia inferiore a un minuto.
Con la quarta colonna, il tempo di esecuzione raggiunge i 4 minuti. Questa volta cerca sull'indice cluster (2 esecuzioni) per lo stesso costo relativo (45%), aggrega tramite una corrispondenza hash (30%), quindi esegue un join hash su #smalltable (0%).
Non sono sicuro del mio prossimo corso d'azione. La mia preoccupazione è che né la ricerca per intervallo di date né il predicato di join siano garantiti o anche con tutta probabilità di ridurre drasticamente il set di risultati. L'intervallo di date nella maggior parte dei casi taglierà solo il 10-15% dei record e il join interno su fk potrebbe filtrare forse il 20-30%.
Come richiesto da Will A, i risultati di sp_spaceused
:
name | rows | reserved | data | index_size | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB
#smalltable è definito come:
create table #endpoints (
pk uniqueidentifier primary key clustered,
stuff varchar(6) null
);
Mentre dbo.hugetable è definito come:
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];
Con il seguente indice definito:
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];
Il campo ID è ridondante, un artefatto di un precedente DBA che ha insistito sul fatto che tutte le tabelle dovessero avere un GUID, senza eccezioni.