J'essaie d'amadouer des performances supplémentaires à partir d'une requête qui accède à une table avec environ 250 millions d'enregistrements. D'après ma lecture du plan d'exécution réel (non estimé), le premier goulot d'étranglement est une requête qui ressemble à ceci:
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;
Voir plus bas pour les définitions des tables et index impliqués.
Le plan d'exécution indique qu'une boucle imbriquée est utilisée sur #smalltable et que l'analyse d'index sur hugetable est exécutée 480 fois (pour chaque ligne de #smalltable). Cela me semble en arrière, j'ai donc essayé de forcer une jointure de fusion à utiliser à la place:
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'index en question (voir ci-dessous pour la définition complète) couvre les colonnes fk (le prédicat de jointure), ajouté (utilisé dans la clause where) & id (inutile) dans l'ordre croissant et inclut la valeur .
Lorsque je fais cela, cependant, la requête passe de 2 1/2 minutes à plus de 9. J'aurais espéré que les indications forceraient une jointure plus efficace qui ne ferait qu'un seul passage sur chaque table, mais clairement pas.
Toute orientation est la bienvenue. Informations supplémentaires fournies si nécessaire.
Mise à jour (2011/06/02)
Après avoir réorganisé l'indexation sur la table, j'ai réalisé des progrès significatifs en matière de performances, mais j'ai rencontré un nouvel obstacle en ce qui concerne la synthèse des données dans l'énorme table. Le résultat est un résumé par mois, qui ressemble actuellement à ce qui suit:
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);
À l'heure actuelle, hugetable a un index clusterisé pk_hugetable (added, fk)
(la clé primaire) et un index non clusterisé dans l'autre sens ix_hugetable (fk, added)
.
Sans la 4e colonne ci-dessus, l'optimiseur utilise une jointure de boucle imbriquée comme auparavant, en utilisant #smalltable comme entrée externe, et une recherche d'index non groupée comme boucle interne (exécutant à nouveau 480 fois). Ce qui me préoccupe, c'est la disparité entre les lignes estimées (12 958,4) et les lignes réelles (74 668 468). Le coût relatif de ces recherches est de 45%. Le temps de course est cependant inférieur à une minute.
Avec la 4ème colonne, le temps de course passe à 4 minutes. Il recherche cette fois sur l'index clusterisé (2 exécutions) pour le même coût relatif (45%), agrège via une correspondance de hachage (30%), puis effectue une jointure de hachage sur #smalltable (0%).
Je ne suis pas sûr de mon prochain plan d'action. Ma préoccupation est que ni la recherche de plage de dates ni le prédicat de jointure ne sont garantis ou même tout ce qui est susceptible de réduire considérablement l'ensemble de résultats. Dans la plupart des cas, la plage de dates ne coupera que 10 à 15% des enregistrements, et la jointure interne sur fk peut filtrer peut-être 20 à 30%.
Comme l'a demandé Will A, les résultats de sp_spaceused
:
name | rows | reserved | data | index_size | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB
#smalltable est défini comme:
create table #endpoints (
pk uniqueidentifier primary key clustered,
stuff varchar(6) null
);
Alors que dbo.hugetable est défini comme:
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];
Avec l'index suivant défini:
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];
Le champ id est redondant, un artefact d'un ancien DBA qui a insisté pour que toutes les tables partout aient un GUID, sans exception.