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 = 1
empujado debajo de él, donde t.isT
está MIN(CONVERT(INT, ar.isT))
. A pesar de esto, el cálculo de selectividad para el isT
predicado puede usarse CSelCalcColumnInInterval
en 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 CSelCalcExpressionComparedToExpression
calculadora:
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 fId
histogramas sin filtrar , modificados por la selectividad del filtro, da 20608 * 20608 * 4.85248e-005 = 20608
filas es muy razonable.
Seguir este cálculo significaría usar la calculadora en CSelCalcSimpleJoinWithDistinctCounts
lugar de CSelCalcExpressionComparedToExpression
. No hay una forma documentada de hacer esto, pero si tiene curiosidad, puede habilitar el indicador de rastreo no documentado 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):
El comportamiento de la estimación de cardinalidad de unión con CSelCalcExpressionComparedToExpression
tambié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 GbAggAfterJoinSel
mueve 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.