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.
fuente
Respuestas:
Su
ix_hugetable
aspecto es bastante inútil porque:Además: - agregado o fk debe ser el primero - ID es primero = poco uso
Intente cambiar la clave agrupada en
(added, fk, id)
y soltarix_hugetable
. Ya lo has intentado(fk, added, id)
. Si nada más, ahorrará mucho espacio en disco y mantenimiento de índiceOtra opción podría ser probar la sugerencia FORCE ORDER con formas de orden de tabla y sin sugerencias JOIN / INDEX. Intento no utilizar personalmente las sugerencias JOIN / INDEX porque eliminas las opciones para el optimizador. Hace muchos años me dijeron (seminario con un SQL Guru) que la sugerencia FORCE ORDER puede ayudar cuando tienes una mesa enorme ÚNETE a una mesa pequeña: YMMV 7 años después ...
Ah, y háganos saber dónde vive el DBA para que podamos organizar un ajuste de percusión
Editar, después de la actualización del 02 de junio
La cuarta columna no forma parte del índice no agrupado, por lo que utiliza el índice agrupado.
Intente cambiar el índice NC para INCLUIR la columna de valor para que no tenga que acceder a la columna de valor para el índice agrupado
Nota: Si el valor no es anulable, es lo mismo que
COUNT(*)
semánticamente. Pero para SUM necesita el valor real , no la existencia .Como ejemplo, si cambia
COUNT(value)
aCOUNT(DISTINCT value)
sin cambiar el índice, debería interrumpir la consulta nuevamente porque tiene que procesar el valor como un valor, no como una existencia.La consulta necesita 3 columnas: agregado, fk, valor. Los primeros 2 se filtran / unen, al igual que las columnas clave. el valor solo se usa para que se pueda incluir. Uso clásico de un índice de cobertura.
fuente
Defina un índice
hugetable
en solo laadded
columna.Los DB utilizarán un índice de varias partes (varias columnas) solo a la derecha de la lista de columnas, ya que tiene valores contando desde la izquierda. Su consulta no especifica
fk
en la cláusula where de la primera consulta, por lo que ignora el índice.fuente
Este es el orden que esperaría que utilice el optimizador de consultas, suponiendo que un bucle se una en la elección correcta. La alternativa es realizar un bucle de 250 millones de veces y realizar una búsqueda en la tabla #temp cada vez, lo que bien podría llevar horas / días.
El índice que está obligando a utilizar en la combinación de mezcla es más o menos 250 millones de filas * 'el tamaño de cada fila' - no es pequeña, por lo menos un par de GB. A juzgar por el
sp_spaceused
resultado, 'un par de GB' podría ser un eufemismo: la unión MERGE requiere que rastree a través del índice, que será muy intensivo en E / S.fuente
Tu índice es incorrecto. Ver índices dos y no .
Tal como están las cosas, su único índice útil es el de la clave principal de la tabla pequeña. Por lo tanto, el único plan razonable es escanear la tabla pequeña y anidar el desorden con la enorme.
Intente agregar un índice agrupado en
hugetable(added, fk)
. Esto debería hacer que el planificador busque filas aplicables de la tabla grande, y anide el bucle o la combinación para unirlas con la tabla pequeña.fuente