Conseil de cardinalité SQL Server


14

Existe-t-il un moyen d'injecter une estimation de cardinalité dans un optimiseur SQL Server (n'importe quelle version)?

c'est-à-dire quelque chose de similaire à l'indice de cardinalité d'Oracle.

Ma motivation est motivée par l'article, à quel point les optimiseurs de requêtes sont-ils vraiment bons? [1] , où ils testent l'influence de l'estimateur de cardinalité sur la sélection d'un mauvais plan. Par conséquent, il suffirait de forcer le serveur SQL à «estimer» les cardinalités précisément pour les requêtes complexes.


[1] Leis, Viktor et al. "Quelle est la qualité des optimiseurs de requêtes, vraiment?"
Actes de la dotation VLDB 9.3 (2015): 204-215.

Réponses:


10

Vous pouvez obtenir quelque chose de similaire à l' CARDINALITYindice d'Oracle en utilisant stratégiquement TOPet une fonction définie par l'utilisateur appelée MANY() développée par Adam Machanic . Travaillons à travers quelques exemples. J'utilise la base de données AdventureWorks disponible gratuitement. Supposons que j'ai vraiment besoin de contrôler le nombre de lignes renvoyées par la thtable dérivée dans la requête suivante:

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;

En l'état, j'obtiens une estimation de 113443 lignes:

requête de départ

Si je dois réduire l'estimation à partir de, thje peux l'utiliser TOPavec l' OPTIMIZE FORindicateur de requête pour définir un objectif de ligne. Voici une façon de procéder:

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));

Nous pouvons voir que l'estimation n'est que d'une ligne:

Estimation sur 1 ligne

J'ai défini @row_goalla BIGINTvaleur la plus élevée possible pour éviter de modifier les résultats. L' OPTIMIZE FORindicateur de requête indique à l'optimiseur d'optimiser la requête comme si@row_goal était égale à 1. J'obtiendrai les mêmes résultats mais la requête sera optimisée différemment.

Augmenter une estimation de cardinalité est plus délicat. Nous ne pouvons pas simplement augmenter la valeur de TOPcar l'optimiseur se rendra compte qu'il ne renverra pas suffisamment de lignes. Cependant, nous pouvons utiliser la MANY()fonction pour ajouter des lignes à l'estimation. Notez que la MANY()fonction renverra toujours 0 ligne mais l'estimation de ligne qui en résulte change avec le paramètre d'entrée. Supposons que vous deviez augmenter l'estimation de ligne du tableau dérivé de 10X. Une façon d'y parvenir:

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;

Nous pouvons voir que l'estimation est 10X la table de base:

Requête 10X

Le superflu a TOPété ajouté pour empêcher l'optimiseur de déplacer les tables. Sans cela,MANY() fonction peut être appliquée au mauvais endroit du plan.

Il est possible de combiner les deux techniques si vous souhaitez une surestimation précise au lieu de simplement multiplier le nombre de lignes par un facteur. Par exemple, supposons que vous ayez vraiment besoin que l'estimation de la table dérivée soit exactement 1000000 lignes. Une façon d'y parvenir:

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));

Nous pouvons voir que l'estimation est de 1000000 lignes:

1 M rangées

Je dois vous avertir qu'il s'agit de techniques avancées qui ne sont souvent pas nécessaires pour l'optimisation des requêtes. Si vous souhaitez en savoir plus, je vous recommande de regarder Clash of the Row Goals présenté par Adam Machanic.


Fonction dbo.Many

-- 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

9

Il n'y a aucun moyen d'injecter une estimation de cardinalité directement dans l'optimiseur, mais en fonction de ce que vous souhaitez réaliser, il existe quelques options.

Vous pouvez utiliser un OPTION (FAST N)indice de requête pour introduire des objectifs de ligne, et éventuellement réécrire votre requête à l'aide de CTE ou de sous-requêtes pour injecter des TOP...ORDER BYobjectifs de ligne dans différentes parties de votre plan d'exécution, mais je ne suis pas sûr de l'efficacité de votre requête résultante lorsque vous démarrerez jouer avec les constructions plus complexes.

Voir À l'intérieur de l'optimiseur: Objectifs de ligne en profondeur pour une explication plus approfondie.

Si vous voulez influencer les opérateurs choisis par l'optimiseur, vous n'avez pas besoin d'essayer d'injecter des estimations de cardinalité, mais vous pouvez utiliser des choses comme OPTION (MERGE JOIN)ou OPTION (HASH JOIN)par exemple pour forcer les opérateurs de jointure physiques.

Cet article explique plus en détail comment influencer un plan à l'aide de conseils: Contrôle des plans d'exécution avec des conseils

Si vous souhaitez corriger un plan, vous pouvez également utiliser un guide de plan.

Encore une fois, il n'est pas clair quel est votre cas d'utilisation réel, et votre kilométrage peut varier en utilisant ces techniques. Dans de nombreux cas, il est préférable de laisser l’optimiseur décider et de vous assurer que vous disposez de statistiques à jour afin que l’optimiseur puisse prendre une décision éclairée.


Suggestion Microsoft Connect pertinente: permet de spécifier un indice de sélectivité du filtre dans les requêtes par xor88. Microsoft a répondu:

Merci pour les commentaires. Je peux voir l'avantage potentiel de cela. En général, nous nous efforçons de rendre notre comportement automatique aussi bon que possible et d'éviter la nécessité de ce type d'indication, mais bien sûr, nous avons de nombreuses autres indications. Nous considérerons cela pour une future version mais ce serait au-delà de la version Denali (11.0).

Cordialement,
Eric Hanson
Gestionnaire de programmes
SQL Server Query Processing


3

Vous pouvez utiliser l' OPTIMIZE FORindicateur de requête SQL Server pour contraindre l'estimation de cardinalité basée sur des valeurs suggérées au lieu d'utiliser la valeur réelle (paramètres) ou la valeur inconnue (variables) pendant la compilation. Voir le rubrique Conseils sur requêtes dans la documentation de SQL Server pour plus de détails.

Par exemple, la requête ci-dessous estimera le nombre de lignes sur la base de l'histogramme de statistiques à partir de valeurs suggérées au lieu de la cardinalité moyenne globale comme elle le ferait autrement avec des 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 même, l'indice peut être utilisé pour les paramètres afin que les estimations soient basées sur l'histogramme des statistiques à partir des valeurs indiquées au lieu des valeurs réelles des paramètres lors de la compilation.

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;

Le UNKNOWNmot clé peut être spécifié au lieu d'un littéral dans l'indice pour utiliser la cardinalité moyenne globale au lieu d'estimer en fonction de la valeur réelle du paramètre et de l'histogramme des statistiques.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.