¿Cómo se pueden mejorar las estimaciones de filas para reducir las posibilidades de derrames a tempdb?

11

Me doy cuenta de que cuando hay eventos de derrame a tempdb (que causan consultas lentas), a menudo las estimaciones de fila están muy lejos para una unión en particular. He visto que se producen eventos de derrame con combinaciones de combinación y hash y, a menudo, aumentan el tiempo de ejecución de 3x a 10x. Esta pregunta se refiere a cómo mejorar las estimaciones de filas bajo el supuesto de que reducirá las posibilidades de eventos de derrames.

Número real de filas 40k.

Para esta consulta, el plan muestra una estimación de filas incorrectas (11.3 filas):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Para esta consulta, el plan muestra una buena estimación de filas (56k filas):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

¿Se pueden agregar estadísticas o sugerencias para mejorar las estimaciones de filas para el primer caso? Intenté agregar estadísticas con valores de filtro particulares (propiedad = 2840) pero no pude obtener la combinación correcta o tal vez se está ignorando porque el ObjectId es desconocido en el momento de la compilación y podría estar eligiendo un promedio sobre todos los ObjectIds.

¿Hay algún modo en el que primero haga la consulta de la sonda y luego la use para determinar las estimaciones de fila o debe volar a ciegas?

Esta propiedad particular tiene muchos valores (40k) en algunos objetos y cero en la gran mayoría. Estaría contento con una pista donde se podría especificar el número máximo esperado de filas para una unión determinada. Este es un problema generalmente inquietante porque algunos parámetros pueden determinarse dinámicamente como parte de la unión o estarían mejor ubicados dentro de una vista (no se admiten variables).

¿Hay algún parámetro que pueda ajustarse para minimizar la posibilidad de derrames en tempdb (por ejemplo, memoria mínima por consulta)? El plan robusto no tuvo efecto en la estimación.

Editar 2013.11.06 : Respuesta a comentarios e información adicional:

Aquí están las imágenes del plan de consulta. Las advertencias son sobre el predicado cardinalidad / búsqueda con el convert ():

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Según el comentario de @Aaron Bertrand, intenté reemplazar el convert () como prueba:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

ingrese la descripción de la imagen aquí

Como un punto de interés extraño pero exitoso, también le permitió cortocircuitar la búsqueda:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

ingrese la descripción de la imagen aquí

Ambos enumeran una búsqueda de clave adecuada, pero solo los primeros enumeran una "Salida" de ObjectId. ¿Supongo que eso indica que el segundo es realmente un cortocircuito?

¿Alguien puede verificar si alguna vez se realizan sondas de una sola fila para ayudar con las estimaciones de fila? Parece incorrecto limitar la optimización solo a estimaciones de histograma cuando una búsqueda PK de una sola fila puede mejorar en gran medida la precisión de la búsqueda en el histograma (especialmente si hay potencial o historial de derrames). Cuando hay 10 de estas subuniones en una consulta real, idealmente estarían sucediendo en paralelo.

Una nota al margen, dado que sql_variant almacena su tipo base (SQL_VARIANT_PROPERTY = BaseType) dentro del campo en sí, esperaría que un convert () sea casi sin costo siempre que sea convertible "directamente" (por ejemplo, no cadena a decimal, sino int a int o tal vez int para bigint). Como eso no se conoce en el momento de la compilación, pero puede ser conocido por el usuario, tal vez una función "AssumeType (type, ...)" para sql_variants les permita ser tratados de manera más transparente.

crokusek
fuente
1
Una primera suposición sería que la conversión a bigint está descartando sus estimaciones (el plan de consulta tendrá una advertencia al respecto en SQL Server 2012) pero, por otro lado, su subconsulta nunca podría devolver nada más que 0 o 1 filas para una consulta exitosa Sería interesante ver los planes de consulta que tiene. Quizás como un enlace a la versión XML.
Mikael Eriksson
2
¿Qué gana al tener la subconsulta en línea? Sugeriría sacarlo por separado es más claro en general, y dado que conduce a mejores estimaciones, ¿por qué no usar ese método?
Aaron Bertrand
2
¿Qué versión de SQL Server? ¿Puede proporcionar tablas e índices DDL y blobs de estadísticas (una y varias columnas) para las tablas para que podamos ver los detalles del problema? Dividir la consulta usando declare @a bigint = como lo ha hecho me parece una solución natural, ¿por qué es inaceptable?
Paul White 9
2
Supongo que su diseño es un diseño EAV (muy simple) que lo obliga a usar CONVERT()en columnas y luego unirlas. Esto ciertamente no es eficiente en la mayoría de los casos. En este caso en particular, solo se debe convertir un valor, por lo que probablemente no sea un problema, pero ¿qué índices tiene en la tabla? Los diseños de EAV generalmente funcionan bien, solo con una indexación adecuada (lo que significa una gran cantidad de índices en las tablas generalmente estrechas).
ypercubeᵀᴹ
@Paul White, en cuanto a la separación ... está bien como solución para este caso. Pero para los más generales / complejos, en su mayoría no quiero renunciar a la paralelización y la legibilidad. Digamos que tengo 10 de estos como subconsultas (algunas son más complejas) dentro de una consulta, pero solo 5 deben estar "maduras" antes de que pueda comenzar el resto de la consulta; me gustaría evitar tener que averiguar cuáles son esas 5.
crokusek

Respuestas:

7

No comentaré sobre derrames, tempdb o sugerencias porque la consulta parece bastante simple para necesitar tanta consideración. Creo que el optimizador de SQL-Server hará su trabajo bastante bien, si hay índices adecuados para la consulta.

Y su división en dos consultas es buena, ya que muestra qué índices serán útiles. La primera parte:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

necesita un índice para (PropertyId, ObjectId, Sequence)incluir el Value. Lo haría UNIQUEpara estar a salvo. La consulta arrojaría un error de todos modos durante el tiempo de ejecución si se devuelven más de una fila, por lo que es bueno asegurarse de antemano que esto no sucederá, con el índice único:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

La segunda parte de la consulta:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

necesita un índice que (PropertyId, ObjectId)incluya Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Si no se mejora la eficiencia o si estos índices no se utilizaron o aún existen diferencias en las estimaciones de filas, entonces sería necesario investigar más a fondo en esta consulta.

En ese caso, las conversiones (necesarias del diseño EAV y el almacenamiento de diferentes tipos de datos en las mismas columnas) son una causa probable y su solución de división (como comentan @AAron Bertrand y @Paul White) la consulta en dos partes parece natural y el camino a seguir. Un rediseño para tener diferentes tipos de datos en sus respectivas columnas podría ser otro.

ypercubeᵀᴹ
fuente
Las tablas tenían índices de cobertura, debería haberlo dicho en la pregunta. El ejemplo es realmente una subunión que alimenta una consulta más grande, razón por la cual hay tanto alboroto sobre los derrames de tempdb.
crokusek
5

Como respuesta parcial a la pregunta explícita sobre cómo mejorar las estadísticas ...

Tenga en cuenta que las estimaciones de filas incluso para el caso separado por separado todavía están desactivadas en 10X (4k frente a 40k esperado).

El histograma de estadísticas probablemente se extendió demasiado para esa propiedad porque es una tabla larga (vertical) de 3.5M filas y esa propiedad particular es extremadamente escasa.

Cree estadísticas adicionales (algo redundantes con las estadísticas IX) para la propiedad dispersa:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Los originales:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Con convert () eliminado (adecuado):

ingrese la descripción de la imagen aquí

Con convert () eliminado (short-ciruit):

ingrese la descripción de la imagen aquí

Aún apagado por ~ 2X probablemente porque> 99.9% de los objetos no tienen la Propiedad 2840 definida en absoluto. De hecho, solo para este caso de prueba, la propiedad existe solo en 1 de 200k Objetos distintos de la tabla de filas de 3.5M. Es sorprendente que se haya acercado tanto. Ajustando el filtro para que tenga menos ObjectIds,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Hmm, no hay cambios ... Respaldo que agregó "con exploración completa" al final de las estadísticas (podría ser la razón por la que los dos anteriores no funcionaron) y sí:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

ingrese la descripción de la imagen aquí

Hurra. Por lo tanto, en una tabla altamente vertical con un IX que cubre ampliamente, agregar estadísticas filtradas adicionales parece ser una gran mejora (especialmente para combinaciones de teclas dispersas pero muy variantes).

crokusek
fuente
Un enlace a algunos problemas problemáticos con estadísticas de varias columnas.
crokusek