sql >> Base de Datos >  >> RDS >> Database

Estimación de cardinalidad:combinación de estadísticas de densidad

Este artículo muestra cómo SQL Server combina la información de densidad de varias estadísticas de una sola columna para producir una estimación de cardinalidad para una agregación en varias columnas. Se espera que los detalles sean interesantes en sí mismos. También brindan información sobre algunos de los enfoques y algoritmos generales utilizados por el estimador de cardinalidad.

Considere la siguiente consulta de base de datos de muestra de AdventureWorks, que enumera la cantidad de artículos de inventario de productos en cada contenedor en cada estante del almacén:

SELECT 
    INV.Shelf, 
    INV.Bin, 
    COUNT_BIG(*)
FROM Production.ProductInventory AS INV
GROUP BY 
    INV.Shelf,
    INV.Bin
ORDER BY 
    INV.Shelf,
    INV.Bin;

El plan de ejecución estimado muestra 1069 filas que se leen de la tabla, ordenadas en Shelf y Bin orden, luego agregado usando un operador Stream Aggregate:

Plan de ejecución estimado

La pregunta es, ¿cómo llegó el optimizador de consultas de SQL Server a la estimación final de 744 filas?

Estadísticas disponibles

Al compilar la consulta anterior, el optimizador de consultas creará estadísticas de una sola columna en el Shelf y Bin columnas, si aún no existen estadísticas adecuadas. Entre otras cosas, estas estadísticas proporcionan información sobre el número de valores de columna distintos (en el vector de densidad):

DBCC SHOW_STATISTICS 
(
    [Production.ProductInventory], 
    [Shelf]
)
WITH DENSITY_VECTOR;
 
DBCC SHOW_STATISTICS 
(
    [Production.ProductInventory], 
    [Bin]
)
WITH DENSITY_VECTOR;

Los resultados se resumen en la siguiente tabla (la tercera columna se calcula a partir de la densidad):

Columna Densidad 1 / Densidad
Estante 0,04761905 21
Papelera 0,01612903 62

Información de vector de densidad de estantes y contenedores

Como señala la documentación, el recíproco de la densidad es el número de valores distintos en la columna. A partir de la información de estadísticas que se muestra arriba, SQL Server sabe que había 21 Shelf distintos valores y 62 Bin distintos valores en la tabla, cuando se recopilaron las estadísticas.

La tarea de estimar el número de filas producidas por un GROUP BY La cláusula es trivial cuando solo está involucrada una sola columna (suponiendo que no haya otros predicados). Por ejemplo, es fácil ver que GROUP BY Shelf producirá 21 filas; GROUP BY Bin producirá 62.

Sin embargo, no está claro de inmediato cómo SQL Server puede estimar la cantidad de (Shelf, Bin) distintos combinaciones para nuestro GROUP BY Shelf, Bin consulta. Para plantear la pregunta de una manera ligeramente diferente:dados 21 estantes y 62 recipientes, ¿cuántas combinaciones únicas de estantes y recipientes habrá? Dejando a un lado los aspectos físicos y otros conocimientos humanos del dominio del problema, la respuesta podría estar entre max(21, 62) =62 y (21 * 62) =1302. Sin más información, no hay una manera obvia de saber dónde lanzar una estimación en ese rango.

Sin embargo, para nuestra consulta de ejemplo, SQL Server estima 744.312 filas (redondeadas a 744 en la vista Plan Explorer), pero ¿sobre qué base?

Evento extendido de estimación de cardinalidad

La forma documentada de analizar el proceso de estimación de cardinalidad es usar el Evento extendido query_optimizer_estimate_cardinality (a pesar de estar en el canal de "depuración"). Mientras se ejecuta una sesión que recopila este evento, los operadores del plan de ejecución obtienen una propiedad adicional StatsCollectionId que vincula las estimaciones de los operadores individuales con los cálculos que las produjeron. Para nuestra consulta de ejemplo, el ID de colección de estadísticas 2 está vinculado a la estimación de cardinalidad para el grupo por operador agregado.

El resultado relevante del evento extendido para nuestra consulta de prueba es:

<data name="calculator">
    <type name="xml" package="package0"></type>
    <value>
    <CalculatorList>
        <DistinctCountCalculator CalculatorName="CDVCPlanLeaf" SingleColumnStat="Shelf,Bin" />
    </CalculatorList>
    </value>
</data>
<data name="stats_collection">
    <type name="xml" package="package0"></type>
    <value>
    <StatsCollection Name="CStCollGroupBy" Id="2" Card="744.31">
        <LoadedStats>
        <StatsInfo DbId="6" ObjectId="258099960" StatsId="3" />
        <StatsInfo DbId="6" ObjectId="258099960" StatsId="4" />
        </LoadedStats>
    </StatsCollection>
    </value>
</data>

Seguro que hay información útil allí.

Podemos ver que la clase de calculadora de valores distintos de la hoja del plan (CDVCPlanLeaf ), usando estadísticas de una sola columna en Shelf y Bin como entradas. El elemento de recopilación de estadísticas hace coincidir este fragmento con el id (2) que se muestra en el plan de ejecución, que muestra la cardinalidad estimada de 744,31 , y también más información sobre los ID de objetos de estadísticas utilizados.

Desafortunadamente, no hay nada en la salida del evento que diga exactamente cómo la calculadora llegó a la cifra final, que es lo que realmente nos interesa.

Combinar recuentos distintos

Yendo por una ruta menos documentada, podemos solicitar un plan estimado para la consulta con indicadores de seguimiento 2363 y 3604 habilitado:

SELECT 
    INV.Shelf, 
    INV.Bin, 
    COUNT_BIG(*)
FROM Production.ProductInventory AS INV
GROUP BY 
    INV.Shelf,
    INV.Bin
ORDER BY 
    INV.Shelf,
    INV.Bin
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363);

Esto devuelve información de depuración a la pestaña Mensajes en SQL Server Management Studio. La parte interesante se reproduce a continuación:

Begin distinct values computation
Input tree:
  LogOp_GbAgg OUT(QCOL: [INV].Shelf,QCOL: [INV].Bin,COL: Expr1001 ,) BY(QCOL: [INV].Shelf,QCOL: [INV].Bin,)
      CStCollBaseTable(ID=1, CARD=1069 TBL: Production.ProductInventory AS TBL: INV)
      AncOp_PrjList 
          AncOp_PrjEl COL: Expr1001 
              ScaOp_AggFunc stopCountBig
                  ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=0)

Plan for computation:
  CDVCPlanLeaf
      0 Multi-Column Stats, 2 Single-Column Stats, 0 Guesses

Loaded histogram for column QCOL: [INV].Shelf from stats with id 3
Loaded histogram for column QCOL: [INV].Bin from stats with id 4

Using ambient cardinality 1069 to combine distinct counts:
  21
  62

Combined distinct count: 744.312
Result of computation: 744.312

Stats collection generated: 
  CStCollGroupBy(ID=2, CARD=744.312)
      CStCollBaseTable(ID=1, CARD=1069 TBL: Production.ProductInventory AS TBL: INV)

End distinct values computation

Esto muestra la misma información que el evento extendido en un formato (posiblemente) más fácil de consumir:

  • El operador relacional de entrada para calcular una estimación de cardinalidad para (LogOp_GbAgg – grupo lógico por agregado)
  • La calculadora utilizada (CDVCPlanLeaf ) y estadísticas de entrada
  • Los detalles de la recopilación de estadísticas resultante

La nueva información interesante es la parte sobre el uso de la cardinalidad ambiental para combinar recuentos distintos .
Esto muestra claramente que se usaron los valores 21, 62 y 1069, pero (frustrantemente) aún no se sabe exactamente qué cálculos se realizaron para llegar a 744.312 resultado.

¡Al depurador!

Adjuntar un depurador y usar símbolos públicos nos permite explorar en detalle la ruta del código que se siguió al compilar la consulta de ejemplo.
La siguiente instantánea muestra la parte superior de la pila de llamadas en un punto representativo del proceso:

MSVCR120!log
sqllang!OdblNHlogN
sqllang!CCardUtilSQL12::ProbSampleWithoutReplacement
sqllang!CCardUtilSQL12::CardDistinctMunged
sqllang!CCardUtilSQL12::CardDistinctCombined
sqllang!CStCollAbstractLeaf::CardDistinctImpl
sqllang!IStatsCollection::CardDistinct
sqllang!CCardUtilSQL12::CardGroupByHelperCore
sqllang!CCardUtilSQL12::PstcollGroupByHelper
sqllang!CLogOp_GbAgg::PstcollDeriveCardinality
sqllang!CCardFrameworkSQL12::DeriveCardinalityProperties

Hay algunos detalles interesantes aquí. Trabajando de abajo hacia arriba, vemos que la cardinalidad se deriva utilizando el CE actualizado (CCardFrameworkSQL12 ) disponible en SQL Server 2014 y versiones posteriores (el CE original es CCardFrameworkSQL7 ), para el grupo por operador lógico agregado (CLogOp_GbAgg ).

Calcular la cardinalidad distinta implica combinar (mungear) múltiples entradas, usando muestreo sin reemplazo.

La referencia a H y un logaritmo (natural) en el segundo método desde arriba muestra el uso de la entropía de Shannon en el cálculo:

Entropía de Shannon

La entropía se puede utilizar para estimar la correlación informativa (información mutua) entre dos estadísticas:

Información mutua

Juntando todo esto, podemos construir un script de cálculo T-SQL que coincida con la forma en que SQL Server usa el muestreo sin reemplazo, la entropía de Shannon y la información mutua. para producir la estimación de cardinalidad final.

Comenzamos con los números de entrada (cardinalidad ambiental y el número de valores distintos en cada columna):

DECLARE 
    @Card float = 1069,
    @Distinct1 float = 21,
    @Distinct2 float = 62;

La frecuencia de cada columna es el número promedio de filas por valor distinto:

DECLARE
    @Frequency1 float = @Card / @Distinct1,
    @Frequency2 float = @Card / @Distinct2;

El muestreo sin reemplazo (SWR) es una simple cuestión de restar el número promedio de filas por valor distinto (frecuencia) del número total de filas:

DECLARE
    @SWR1 float = @Card - @Frequency1,
    @SWR2 float = @Card - @Frequency2,
    @SWR3 float = @Card - @Frequency1 - @Frequency2;

Calcule las entropías (N log N) y la información mutua:

DECLARE
    @E1 float = (@SWR1 + 0.5) * LOG(@SWR1),
    @E2 float = (@SWR2 + 0.5) * LOG(@SWR2),
    @E3 float = (@SWR3 + 0.5) * LOG(@SWR3),
    @E4 float = (@Card + 0.5) * LOG(@Card);
 
-- Using logarithms allows us to express
-- multiplication as addition and division as subtraction
DECLARE
    @MI float = EXP(@E1 + @E2 - @E3 - @E4);

Ahora que hemos estimado cuán correlacionados están los dos conjuntos de estadísticas, podemos calcular la estimación final:

SELECT (1e0 - @MI) * @Distinct1 * @Distinct2;

El resultado del cálculo es 744,311823994677, que es 744,312 redondeado a tres decimales.

Para mayor comodidad, aquí está el código completo en un bloque:

DECLARE 
    @Card float = 1069,
    @Distinct1 float = 21,
    @Distinct2 float = 62;
 
DECLARE
    @Frequency1 float = @Card / @Distinct1,
    @Frequency2 float = @Card / @Distinct2;
 
-- Sample without replacement
DECLARE
    @SWR1 float = @Card - @Frequency1,
    @SWR2 float = @Card - @Frequency2,
    @SWR3 float = @Card - @Frequency1 - @Frequency2;
 
-- Entropy
DECLARE
    @E1 float = (@SWR1 + 0.5) * LOG(@SWR1),
    @E2 float = (@SWR2 + 0.5) * LOG(@SWR2),
    @E3 float = (@SWR3 + 0.5) * LOG(@SWR3),
    @E4 float = (@Card + 0.5) * LOG(@Card);
 
-- Mutual information
DECLARE
    @MI float = EXP(@E1 + @E2 - @E3 - @E4);
 
-- Final estimate
SELECT (1e0 - @MI) * @Distinct1 * @Distinct2;

Reflexiones finales

La estimación final es imperfecta en este caso:la consulta de ejemplo devuelve 441 filas.

Para obtener una estimación mejorada, podríamos proporcionar al optimizador mejor información sobre la densidad del Bin y Shelf columnas utilizando una estadística multicolumna. Por ejemplo:

CREATE STATISTICS stat_Shelf_Bin 
ON Production.ProductInventory (Shelf, Bin);

Con esa estadística en su lugar (ya sea como se indica o como un efecto secundario de agregar un índice de varias columnas similar), la estimación de cardinalidad para la consulta de ejemplo es exactamente correcta. Sin embargo, es raro calcular una agregación tan simple. Con predicados adicionales, la estadística de varias columnas puede ser menos eficaz. Sin embargo, es importante recordar que la información de densidad adicional proporcionada por las estadísticas de varias columnas puede ser útil para agregaciones (así como comparaciones de igualdad).

Sin una estadística de varias columnas, una consulta agregada con predicados adicionales aún puede usar la lógica esencial que se muestra en este artículo. Por ejemplo, en lugar de aplicar la fórmula a la cardinalidad de la tabla, se puede aplicar a los histogramas de entrada paso a paso.

Contenido relacionado:Estimación de cardinalidad para un predicado en una expresión COUNT