Sugerencia de cardinalidad de SQL Server

14

¿Hay alguna manera de 'inyectar' una estimación de cardinalidad en un optimizador de SQL Server (cualquier versión)?

es decir, algo similar a la sugerencia de cardinalidad de Oracle.

Mi motivación es impulsada por el artículo, ¿Qué tan buenos son realmente los optimizadores de consultas? [1] , donde prueban la influencia del estimador de cardinalidad en una selección de un mal plan. Por lo tanto, sería suficiente si pudiera forzar al SQL Server a 'estimar' las cardinalidades precisamente para consultas complejas.


[1] Leis, Viktor, y col. "¿Qué tan buenos son realmente los optimizadores de consultas?"
Actas de la dotación VLDB 9.3 (2015): 204-215.

Radim Bača
fuente

Respuestas:

10

Puede obtener algo similar a la CARDINALITYsugerencia de Oracle mediante el uso estratégico TOPy una función definida por el usuario llamada MANY() desarrollada por Adam Machanic . Analicemos algunos ejemplos. Estoy usando la base de datos AdventureWorks disponible gratuitamente. Supongamos que realmente necesito controlar el número de filas devueltas por la thtabla derivada en la siguiente consulta:

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
) th ON p.ProductID = th.ProductID;

Como es, obtengo una estimación de 113443 filas:

consulta de inicio

Si necesito reducir la estimación th, puedo usarla TOPjunto con la OPTIMIZE FORsugerencia de consulta para establecer un objetivo de fila. Aquí hay una forma de hacerlo:

DECLARE @row_goal BIGINT = 9223372036854775807;
SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1));

Podemos ver que la estimación es solo 1 fila:

Estimación de 1 fila

Configuré @row_goalel mayor BIGINTvalor posible para evitar cambiar los resultados. La OPTIMIZE FORsugerencia de la consulta indica al optimizador que optimice la consulta como si fuera @row_goaligual a 1. Obtendré los mismos resultados pero la consulta se optimizará de manera diferente.

Aumentar una estimación de cardinalidad es más complicado. No podemos simplemente aumentar el valor TOPporque el optimizador se dará cuenta de que no devolverá suficientes filas. Sin embargo, podemos usar la MANY()función para agregar filas a la estimación. Tenga en cuenta que la MANY()función siempre devolverá 0 filas, pero la estimación de la fila cambia con el parámetro de entrada. Suponga que necesita aumentar la estimación de fila de la tabla derivada en 10X. Una forma de lograr eso:

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (9223372036854775807) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
    LEFT OUTER JOIN dbo.Many(10) AS m ON 1=1
) th ON p.ProductID = th.ProductID;

Podemos ver que la estimación es 10 veces la tabla base:

Consulta 10X

Se TOPagregó lo superfluo para evitar que el optimizador mueva las tablas. Sin ella, la MANY()función puede aplicarse al lugar equivocado en el plan.

Es posible combinar las dos técnicas si desea una sobreestimación precisa en lugar de simplemente multiplicar el número de filas por un factor. Por ejemplo, supongamos que realmente necesita que la estimación de la tabla derivada sea exactamente de 1000000 filas. Una forma de lograr eso:

DECLARE @row_goal BIGINT = 9223372036854775807;

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
    LEFT OUTER JOIN dbo.Many(10) AS m ON
        1=1
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1000000));

Podemos ver que la estimación es de 1000000 filas:

1 M filas

Debo advertirle que estas son técnicas avanzadas que a menudo no son necesarias para la optimización de consultas. Si desea obtener más información, le recomiendo ver Choque de los objetivos de la fila presentado por Adam Machanic.


dbo.Mucha función

-- By Adam Machanic, reproduced with permission
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'Many' AND OBJECT_SCHEMA_NAME(object_id) = 'dbo')
    DROP FUNCTION dbo.Many
GO
CREATE FUNCTION dbo.Many(@n INT)
RETURNS TABLE AS
RETURN
(
    WITH
    a(x) AS
    (
        SELECT
            *
        FROM
        (
            VALUES
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)
        ) AS x0(x)
    )
    SELECT TOP(@n)
        1 AS x
    FROM
        a AS a1,
        a AS a2
    WHERE
        a1.x % 2 = 0
)
GO
Joe Obbish
fuente
9

No hay forma de inyectar una estimación de cardinalidad directamente en el optimizador, pero dependiendo de lo que desee lograr, hay algunas opciones.

Podría usar una OPTION (FAST N)sugerencia de consulta para introducir objetivos de fila y posiblemente reescribir su consulta utilizando CTE o subconsultas para inyectar TOP...ORDER BYobjetivos de fila basados ​​en diferentes partes de su plan de ejecución, pero no estoy seguro de cuán eficiente será su consulta resultante cuando comience jugando con las construcciones más complejas.

Consulte Inside the Optimizer: Row Goals In Depth para obtener una explicación más detallada.

Si quiere influir en los operadores que los picos del optimizador que no es necesario para tratar de inyectar estimaciones de cardinalidad pero se puede usar cosas como OPTION (MERGE JOIN)o OPTION (HASH JOIN), por ejemplo, a la fuerza física operadores de combinación.

Este artículo entra en más detalles sobre cómo influir en un plan usando sugerencias: Control de planes de ejecución con sugerencias

Si desea arreglar un plan, también puede usar una guía de plan.

Nuevamente, no está claro cuál es su caso de uso real, y su millaje puede variar usando estas técnicas. En muchos casos, es mejor dejar que el optimizador decida y asegurarse de tener estadísticas actualizadas para que el optimizador pueda tomar una decisión informada.


Sugerencia relevante de Microsoft Connect: Permitir especificar sugerencia de selectividad de filtro en consultas de xor88. Microsoft respondió:

Gracias por la respuesta. Puedo ver el beneficio potencial de esto. En general, tratamos de hacer que nuestro comportamiento automático sea lo mejor posible y evitar la necesidad de este tipo de pistas, pero, por supuesto, tenemos muchas otras pistas. Consideraremos esto para un lanzamiento futuro, pero estaría más allá de la versión Denali (11.0).

Saludos cordiales,
Eric Hanson
Gerente de programa
SQL Server Query Processing

Tom V - prueba topanswers.xyz
fuente
3

Puede usar la OPTIMIZE FORsugerencia de consulta de SQL Server para forzar la estimación de cardinalidad basada en valores insinuados en lugar de usar el valor real (parámetros) o el valor desconocido (variables) durante la compilación. Consulte el tema Sugerencias de consulta en la documentación de SQL Server para obtener detalles completos.

Por ejemplo, la consulta a continuación estimará los recuentos de filas en función del histograma de estadísticas a partir de valores insinuados en lugar de la cardinalidad promedio general como lo haría con las variables locales.

DECLARE 
      @StartDate datetime = '20150101'
    , @EndDate datetime = '20150102';
SELECT *
FROM dbo.Example
WHERE
    DateColumn BETWEEN  @StartDate AND @EndDate
OPTION(OPTIMIZE FOR(@StartDate = '20100101', @EndDate='20100101'));

De manera similar, la sugerencia se puede usar para parámetros de modo que las estimaciones se basen en el histograma de estadísticas de los valores insinuados en lugar de los valores de los parámetros reales durante la compilación.

DECLARE 
      @StartDate datetime = '20150101'
    , @EndDate datetime = '20150102';
EXECUTE sp_executesql N'SELECT *
        FROM dbo.Example
        WHERE
            DateColumn BETWEEN  @StartDate AND @EndDate
        OPTION(OPTIMIZE FOR(@StartDate = ''20100101'', @EndDate=''20100101''));'
    , N'@StartDate datetime, @EndDate datetime'
    , @StartDate = @StartDate
    , @EndDate = @EndDate;

La UNKNOWNpalabra clave se puede especificar en lugar de un literal en la sugerencia para usar la cardinalidad promedio general en lugar de estimar en función del valor del parámetro real y el histograma de estadísticas.

Dan Guzman
fuente