Estoy tratando de obtener más rendimiento de una consulta que está accediendo a una tabla con ~ 250 millones de registros. Según mi lectura del plan de ejecución real (no estimado), el primer cuello de botella es una consulta que se ve así:
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;
Consulte más abajo las definiciones de las tablas e índices involucrados.
El plan de ejecución indica que se está utilizando un bucle anidado en #smalltable, y que el escaneo del índice sobre la enorme tabla se está ejecutando 480 veces (para cada fila en #smalltable). Esto me parece al revés, así que he intentado forzar una combinación de combinación para que se use en su lugar:
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;
El índice en cuestión (ver más abajo para la definición completa) cubre las columnas fk (el predicado de unión), agregado (usado en la cláusula where) e id (inútil) en orden ascendente, e incluye el valor .
Sin embargo, cuando hago esto, la consulta se extiende de 2 1/2 minutos a más de 9. Hubiera esperado que las sugerencias forzarían una unión más eficiente que solo haga un solo paso sobre cada mesa, pero claramente no.
Cualquier orientación es bienvenida. Información adicional proporcionada si es necesario.
Actualización (02/06/2011)
Después de reorganizar la indexación en la tabla, he logrado avances significativos en el rendimiento, sin embargo, he encontrado un nuevo obstáculo a la hora de resumir los datos en la gran tabla. El resultado es un resumen por mes, que actualmente se parece a lo siguiente:
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);
En la actualidad, hugetable tiene un índice agrupado pk_hugetable (added, fk)
(la clave principal) y un índice no agrupado que va en sentido contrario ix_hugetable (fk, added)
.
Sin la cuarta columna anterior, el optimizador utiliza una unión de bucle anidado como antes, usando #smalltable como entrada externa, y una búsqueda de índice no agrupado como bucle interno (ejecutándose 480 veces de nuevo). Lo que me preocupa es la disparidad entre las filas estimadas (12,958.4) y las filas reales (74,668,468). El costo relativo de estas búsquedas es del 45%. Sin embargo, el tiempo de ejecución es inferior a un minuto.
Con la cuarta columna, el tiempo de ejecución aumenta a 4 minutos. Esta vez busca en el índice agrupado (2 ejecuciones) el mismo costo relativo (45%), agrega a través de una coincidencia hash (30%), luego realiza una unión hash en #smalltable (0%).
No estoy seguro de mi próximo curso de acción. Mi preocupación es que ni la búsqueda de rango de fechas ni el predicado de unión están garantizados o incluso todo lo que probablemente reduzca drásticamente el conjunto de resultados. El rango de fechas en la mayoría de los casos solo recortará tal vez el 10-15% de los registros, y la unión interna en fk puede filtrar tal vez el 20-30%.
Según lo solicitado por Will A, los resultados de sp_spaceused
:
name | rows | reserved | data | index_size | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB
#smalltable se define como:
create table #endpoints (
pk uniqueidentifier primary key clustered,
stuff varchar(6) null
);
Mientras dbo.hugetable se define como:
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 el siguiente índice definido:
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];
El campo id es redundante, un artefacto de un DBA anterior que insistió en que todas las tablas en todas partes deberían tener un GUID, sin excepciones.