SQL Server 2014: ¿alguna explicación para la estimación inconsistente de la cardinalidad de autounión?


27

Considere el siguiente plan de consulta en SQL Server 2014:

ingrese la descripción de la imagen aquí

En el plan de consulta, una unión automática ar.fId = ar.fIdproduce una estimación de 1 fila. Sin embargo, esta es una estimación lógicamente inconsistente: artiene 20,608filas y solo un valor distinto de fId(reflejado con precisión en las estadísticas). Por lo tanto, esta unión produce el producto cruzado completo de filas ( ~424MMfilas), lo que hace que la consulta se ejecute durante varias horas.

Me está costando entender por qué SQL Server presentaría una estimación que se puede demostrar tan fácilmente que es inconsistente con las estadísticas. ¿Algunas ideas?

Investigación inicial y detalles adicionales.

Según la respuesta de Paul aquí , parece que tanto la heurística de SQL 2012 como la de SQL 2014 para estimar la cardinalidad de unión deberían manejar fácilmente una situación en la que deben compararse dos histogramas idénticos.

Comencé con la salida del indicador de seguimiento 2363, pero no pude entenderlo fácilmente. ¿El siguiente fragmento significa que SQL Server está comparando histogramas para fIdy bIdpara estimar la selectividad de una unión que solo usa fId? Si es así, eso obviamente no sería correcto. ¿O estoy leyendo mal la salida del indicador de rastreo?

Plan for computation:
  CSelCalcExpressionComparedToExpression( QCOL: [ar].fId x_cmpEq QCOL: [ar].fId )
Loaded histogram for column QCOL: [ar].bId from stats with id 3
Loaded histogram for column QCOL: [ar].fId from stats with id 1
Selectivity: 0

Tenga en cuenta que se me ocurrieron varias soluciones alternativas, que se incluyen en el script de reproducción completo y reducen esta consulta a milisegundos. Esta pregunta se centra en comprender el comportamiento, cómo evitarlo en futuras consultas y determinar si se trata de un error que debe presentarse ante Microsoft.

Aquí hay una secuencia de comandos de reproducción completa , aquí está el resultado completo de la marca de seguimiento 2363 , y aquí están las definiciones de consulta y tabla en caso de que desee verlas rápidamente sin abrir la secuencia de comandos completa:

WITH cte AS (
    SELECT ar.fId, 
        ar.bId,
        MIN(CONVERT(INT, ar.isT)) AS isT,
        MAX(CONVERT(INT, tcr.isS)) AS isS
    FROM  #SQL2014MinMaxAggregateCardinalityBug_ar ar 
    LEFT OUTER JOIN #SQL2014MinMaxAggregateCardinalityBug_tcr tcr
        ON tcr.rId = 508
        AND tcr.fId = ar.fId
        AND tcr.bId = ar.bId
    GROUP BY ar.fId, ar.bId
)
SELECT s.fId, s.bId, s.isS, t.isS
FROM cte s 
JOIN cte t 
    ON t.fId = s.fId 
    AND t.isT = 1

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_ar (
    fId INT NOT NULL,
    bId INT NOT NULL,
    isT BIT NOT NULL
    PRIMARY KEY (fId, bId)
)

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_tcr (
    rId INT NOT NULL,
    fId INT NOT NULL,
    bId INT NOT NULL,
    isS BIT NOT NULL
    PRIMARY KEY (rId, fId, bId, isS)
)

Respuestas:


23

Me está costando entender por qué SQL Server presentaría una estimación que se puede demostrar tan fácilmente que es inconsistente con las estadísticas.

Consistencia

No hay garantía general de consistencia. Las estimaciones pueden calcularse en diferentes subárboles (pero lógicamente equivalentes) en diferentes momentos, utilizando diferentes métodos estadísticos.

No hay nada de malo en la lógica que dice que unir esos dos subárboles idénticos debería producir un producto cruzado, pero tampoco hay nada que decir que la elección del razonamiento es más sólida que cualquier otra.

Estimación inicial

En su caso específico, la estimación de cardinalidad inicial para la unión no se realiza en dos subárboles idénticos . La forma del árbol en ese momento es:

  LogOp_Join
     LogOp_GbAgg
        LogOp_LeftOuterJoin
           LogOp_Get TBL: ar
           LogOp_Select
              LogOp_Get TBL: tcr
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [tcr] .rId
                 Valor ScaOp_Const = 508
           ScaOp_Logical x_lopAnd
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .fId
                 ScaOp_Identifier [tcr] .fId
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .bId
                 ScaOp_Identifier [tcr] .bId
        AncOp_PrjList 
           AncOp_PrjEl Expr1003 
              ScaOp_AggFunc stopMax
                 ScaOp_Convert int
                    ScaOp_Identifier [tcr] .isS
     LogOp_Select
        LogOp_GbAgg
           LogOp_LeftOuterJoin
              LogOp_Get TBL: ar
              LogOp_Select
                 LogOp_Get TBL: tcr
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [tcr] .rId
                    Valor ScaOp_Const = 508
              ScaOp_Logical x_lopAnd
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .fId
                    ScaOp_Identifier [tcr] .fId
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .bId
                    ScaOp_Identifier [tcr] .bId
           AncOp_PrjList 
              AncOp_PrjEl Expr1006 
                 ScaOp_AggFunc stopMin
                    ScaOp_Convert int
                       ScaOp_Identifier [ar] .isT
              AncOp_PrjEl Expr1007 
                 ScaOp_AggFunc stopMax
                    ScaOp_Convert int
                       ScaOp_Identifier [tcr] .isS
        ScaOp_Comp x_cmpEq
           ScaOp_Identifier Expr1006 
           Valor ScaOp_Const = 1
     ScaOp_Comp x_cmpEq
        ScaOp_Identifier QCOL: [ar] .fId
        ScaOp_Identifier QCOL: [ar] .fId

La primera entrada de unión ha tenido un agregado no proyectado simplificado, y la segunda entrada de unión tiene el predicado t.isT = 1empujado debajo de él, donde t.isTestá MIN(CONVERT(INT, ar.isT)). A pesar de esto, el cálculo de selectividad para el isTpredicado puede usarse CSelCalcColumnInIntervalen un histograma:

  CSelCalcColumnInInterval
      Columna: COL: Expr1006 

Histograma cargado para la columna QCOL: [ar] .isT de las estadísticas con id 3

Selectividad: 4.85248e-005

Colección de estadísticas generada: 
  CStCollFilter (ID = 11, CARD = 1)
      CStCollGroupBy (ID = 10, CARD = 20608)
          CStCollOuterJoin (ID = 9, CARD = 20608 x_jtLeftOuter)
              CStCollBaseTable (ID = 3, CARD = 20608 TBL: ar)
              CStCollFilter (ID = 8, CARD = 1)
                  CStCollBaseTable (ID = 4, CARD = 28 TBL: tcr)

La expectativa (correcta) es que este predicado reduzca 20,608 filas a 1 fila.

Unir estimación

La pregunta ahora es cómo las 20.608 filas de la otra entrada de combinación coincidirán con esta fila:

  LogOp_Join
      CStCollGroupBy (ID = 7, CARD = 20608)
          CStCollOuterJoin (ID = 6, CARD = 20608 x_jtLeftOuter)
              ...

      CStCollFilter (ID = 11, CARD = 1)
          CStCollGroupBy (ID = 10, CARD = 20608)
              ...

      ScaOp_Comp x_cmpEq
          ScaOp_Identifier QCOL: [ar] .fId
          ScaOp_Identifier QCOL: [ar] .fId

Hay varias formas diferentes de estimar la unión en general. Podríamos, por ejemplo:

  • Derive nuevos histogramas en cada operador del plan en cada subárbol, alinéelos en la unión (interpolando los valores de los pasos según sea necesario) y vea cómo coinciden; o
  • Realice una alineación 'gruesa' más simple de los histogramas (utilizando valores mínimos y máximos, no paso a paso); o
  • Calcule selectividades separadas para las columnas de unión solo (desde la tabla base, y sin ningún filtrado), luego agregue el efecto de selectividad de los predicados que no se unen.
  • ...

Dependiendo del estimador de cardinalidad en uso, y algunas heurísticas, podría usarse cualquiera de esos (o una variación). Consulte el Libro blanco de Microsoft Optimización de sus planes de consulta con el Estimador de cardinalidad de SQL Server 2014 para obtener más información.

¿Error?

Ahora, como se señaló en la pregunta, en este caso la combinación de columna simple (simple fId) utiliza la CSelCalcExpressionComparedToExpressioncalculadora:

Plan de cómputo:

  CSelCalcExpressionComparedToExpression [ar] .fId x_cmpEq [ar] .fId

Histograma cargado para la columna QCOL: [ar] .bId de las estadísticas con id 2
Histograma cargado para la columna QCOL: [ar] .fId de estadísticas con id 1

Selectividad: 0

Este cálculo evalúa que unir las 20.608 filas con la 1 fila filtrada tendrá una selectividad cero: ninguna fila coincidirá (se informa como una fila en los planes finales). ¿Esto esta mal? Sí, probablemente haya un error en el nuevo CE aquí. Se podría argumentar que 1 fila coincidirá con todas las filas o ninguna, por lo que el resultado podría ser razonable, pero hay razones para creer lo contrario.

Los detalles son bastante complicados, pero la expectativa de que la estimación se base en fIdhistogramas sin filtrar , modificados por la selectividad del filtro, da 20608 * 20608 * 4.85248e-005 = 20608filas es muy razonable.

Seguir este cálculo significaría usar la calculadora en CSelCalcSimpleJoinWithDistinctCountslugar de CSelCalcExpressionComparedToExpression. No hay una forma documentada de hacer esto, pero si tiene curiosidad, puede habilitar el indicador de rastreo no documentado 9479:

Plan 9479

Tenga en cuenta que la unión final produce 20.608 filas a partir de dos entradas de una sola fila, pero eso no debería ser una sorpresa. Es el mismo plan producido por el CE original bajo TF 9481.

Mencioné que los detalles son complicados (y requieren mucho tiempo para investigar), pero por lo que puedo decir, la causa raíz del problema está relacionada con el predicado rId = 508, con una selectividad cero. Esta estimación cero se eleva a una fila de la manera normal, lo que parece contribuir a la estimación de selectividad cero en la unión en cuestión cuando da cuenta de predicados más bajos en el árbol de entrada (por lo tanto, carga estadísticas para bId).

Al permitir que la unión externa mantenga una estimación del lado interno de la fila cero (en lugar de elevarla a una fila) (para que todas las filas externas califiquen) se obtiene una estimación de unión 'libre de errores' con cualquier calculadora. Si está interesado en explorar esto, el indicador de rastreo no documentado es 9473 (solo):

Plan 9473

El comportamiento de la estimación de cardinalidad de unión con CSelCalcExpressionComparedToExpressiontambién se puede modificar para no tener en cuenta `` bId '' con otro indicador de variación no documentado (9494). Menciono todo esto porque sé que tienes interés en tales cosas; no porque ofrezcan una solución. Hasta que informe el problema a Microsoft y lo resuelvan (o no), expresar la consulta de manera diferente es probablemente la mejor manera de avanzar. Independientemente de si el comportamiento es intencional o no, deberían estar interesados ​​en escuchar acerca de la regresión.

Finalmente, para ordenar otra cosa mencionada en el script de reproducción: la posición final del filtro en el plan de preguntas es el resultado de una exploración basada en el costo que GbAggAfterJoinSelmueve el agregado y el filtro por encima de la unión, ya que la salida de la unión tiene un tamaño tan pequeño número de filas. El filtro estaba inicialmente debajo de la unión, como esperaba.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.