Optimizar la unión en una mesa grande

10

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.

Quick Joe Smith
fuente
¿Podría incluir el resultado de sp_spaceused 'dbo.hugetable', por favor?
Will A
Hecho, agregado justo arriba del inicio de las definiciones de la tabla.
Quick Joe Smith
Seguro que lo es. Su tamaño ridículo es la razón por la que estoy investigando esto.
Quick Joe Smith

Respuestas:

5

Su ix_hugetableaspecto es bastante inútil porque:

  • que es el índice agrupado (PK)
  • INCLUDE no hace ninguna diferencia porque un índice agrupado INCLUYE todas las columnas sin clave (valores sin clave en la hoja más baja = INCLUDEd = qué es un índice agrupado)

Además: - agregado o fk debe ser el primero - ID es primero = poco uso

Intente cambiar la clave agrupada en (added, fk, id)y soltar ix_hugetable. Ya lo has intentado (fk, added, id). Si nada más, ahorrará mucho espacio en disco y mantenimiento de índice

Otra 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

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc
) include(value)

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)a COUNT(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.

gbn
fuente
Hah, tenía en mi cabeza que los índices agrupados y no agrupados se habían añadido y ordenado en diferente orden. No puedo creer que no me haya dado cuenta de eso, casi tanto como no puedo creer que se haya configurado de esta manera en primer lugar. Cambiaré el índice agrupado mañana, luego iré a tomar un café mientras se reconstruye.
Quick Joe Smith
He alterado la indexación y he tenido un golpe con FORCE ORDER en un intento de reducir el número de búsquedas en la tabla grande, pero fue en vano. Mi pregunta ha sido actualizada.
Quick Joe Smith
@Quick Joe Smith: actualizado mi respuesta
gbn
Sí, lo intenté poco después. Debido a que la reconstrucción del índice lleva tanto tiempo, lo olvidé e inicialmente pensé que lo aceleraría haciendo algo completamente ajeno.
Quick Joe Smith
2

Defina un índice hugetableen solo la addedcolumna.

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 fken la cláusula where de la primera consulta, por lo que ignora el índice.

bohemio
fuente
El plan de ejecución muestra que se está buscando el índice (ix_hugetable) . ¿O está diciendo que este índice no es apropiado para la consulta?
Quick Joe Smith
El índice no es apropiado. Quién sabe cómo es "usar el índice". La experiencia me dice que este es tu problema. Pruébalo y cuéntanos cómo te va.
Bohemio
@Quick Joe Smith: ¿probaste la sugerencia de @ Bohemian? ¿Dónde están los resultados?
Lieven Keersmaekers
2
No estoy de acuerdo: la cláusula ON se procesa lógicamente primero y es efectivamente un DONDE en la práctica, por lo que OP tiene que probar ambas columnas primero. Sin indexación en fk en absoluto = exploración de índice agrupado o búsqueda de clave para obtener el valor fk para JOIN. ¿Puede agregar algunas referencias al comportamiento que ha descrito también, por favor? Especialmente para SQL Server dado que tiene poco historial previo respondiendo para este RDBMS. En realidad, -1 en retrospectiva mientras escribo este comentario
gbn
2

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).

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_spaceusedresultado, '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.

Will A
fuente
Tengo entendido que hay 3 tipos de algoritmos de combinación, y que la combinación de combinación tiene el mejor rendimiento cuando el predicado de combinación ordena ambas entradas. Bien o mal, este es el resultado que estoy tratando de obtener.
Quick Joe Smith
2
Pero hay más que esto. Si #smalltable tenía una gran cantidad de filas, una combinación de combinación podría ser apropiada. Si, como su nombre lo indica, tiene un pequeño número de filas, una combinación de bucles podría ser la opción correcta. Imagina que #smalltable tenía una o dos filas, y coincidía con un puñado de filas de la otra tabla; sería difícil justificar una combinación de combinación aquí.
Will A
Me imaginé que había más; Simplemente no sabía qué podría ser eso. La optimización de la base de datos no es exactamente mi punto fuerte, como probablemente ya haya adivinado.
Quick Joe Smith
@Quick Joe Smith: gracias por el sp_spaceused. 75 GB de índice y 18 GB de datos: ¿ix_hugetable no es el único índice en la tabla?
Will A
1
+1 voluntad. El planificador está haciendo lo correcto actualmente. El problema radica en las búsquedas aleatorias de discos debido a la forma en que se agrupan sus tablas.
Denis de Bernardy
1

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.

Denis de Bernardy
fuente
Gracias por ese enlace. Intentaré esto cuando llegue al trabajo mañana.
Quick Joe Smith