Estimation de la cardinalité pour l'opérateur LIKE (variables locales)

24

J'avais l'impression que lors de l'utilisation de l' LIKEopérateur dans tous les optimisations pour des scénarios inconnus, les anciens et les nouveaux CE utilisent une estimation de 9% (en supposant que les statistiques pertinentes sont disponibles et que l'optimiseur de requêtes n'a pas à recourir à des suppositions de sélectivité).

Lors de l'exécution de la requête ci-dessous par rapport à la base de données de crédit, j'obtiens des estimations différentes sous les différents CE. Sous le nouveau CE, je reçois une estimation de 900 lignes que j'attendais, sous l'ancien CE, je reçois une estimation de 241.416 et je ne peux pas comprendre comment cette estimation est dérivée. Quelqu'un peut-il faire la lumière?

-- New CE (Estimate = 900)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName;

-- Forcing Legacy CE (Estimate = 241.416)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName
OPTION (
QUERYTRACEON 9481,
QUERYTRACEON 9292,
QUERYTRACEON 9204,
QUERYTRACEON 3604
);

Dans mon scénario, j'ai déjà la base de données de crédit définie au niveau de compatibilité 120, d'où la raison pour laquelle dans la deuxième requête, j'utilise des indicateurs de trace pour forcer l'héritage CE et pour fournir également des informations sur les statistiques utilisées / prises en compte par l'optimiseur de requête. Je peux voir que les statistiques de la colonne sur le «nom de famille» sont utilisées, mais je ne parviens toujours pas à déterminer comment l'estimation de 241.416 est dérivée.

Je n'ai rien trouvé en ligne autre que cet article d'Itzik Ben-Gan , qui déclare "Lors de l'utilisation du prédicat LIKE dans tous les optimisations pour des scénarios inconnus, l'héritage et les nouveaux CE utilisent une estimation de 9 pour cent.". Les informations contenues dans ce message semblent incorrectes.

Fza
la source

Réponses:

28

La supposition LIKE dans votre cas est basée sur:

  • G: La supposition standard à 9% ( sqllang!x_Selectivity_Like)
  • M: Un facteur de 6 (nombre magique)
  • D: Longueur moyenne des données en octets (à partir des statistiques), arrondie à l'entier

Plus précisément, sqllang!CCardUtilSQL7::ProbLikeGuessutilise:

Selectivity (S) = G / M * LOG(D)

Remarques:

  • Le LOG(D)terme est omis s'il Dest compris entre 1 et 2.
  • Si Dest inférieur à 1 (y compris pour les NULLstatistiques manquantes ou ):
    D = FLOOR(0.5 * maximum column byte length)

Ce genre de bizarrerie et de complexité est tout à fait typique du CE d'origine.

Dans l'exemple de question, la longueur moyenne est de 5 (5,6154 à partir de l' DBCC SHOW_STATISTICSarrondi):

Estimation = 10 000 * (0,09 / 6 * LOG (5)) = 241,416

Autres exemples de valeurs:

 D   = Estimation utilisant la formule pour S
 15 = 406,208
 14 = 395,859
 13 = 384,742
 12 = 372,736
 11 = 359,684
 10 = 345,388
 09 = 329,584
 08 = 311,916
 07 = 291,887
 06 = 268,764
 05 = 241,416
 04 = 207,944
 03 = 164,792
 02 = 150.000 (LOG non utilisé)
 01 = 150.000 (LOG non utilisé)
 00 = 291.887 (LOG 7) / * FLOOR (0.5 * 15) [15 puisque le nom de famille est varchar (15)] * /

Banc d'essai

DECLARE
    @CharLength integer = 5, -- Set length here
    @Counter integer = 1;

CREATE TABLE #T (c1 varchar(15) NULL);

-- Add 10,000 rows
SET NOCOUNT ON;
SET STATISTICS XML OFF;

BEGIN TRANSACTION;
WHILE @Counter <= 10000
BEGIN
    INSERT #T (c1) VALUES (REPLICATE('X', @CharLength));
    SET @Counter = @Counter + 1;
END;
COMMIT TRANSACTION;

SET NOCOUNT OFF;
SET STATISTICS XML ON;

-- Test query
DECLARE @Like varchar(15);
SELECT * FROM #T AS T 
WHERE T.c1 LIKE @Like;

DROP TABLE #T;
Paul White dit GoFundMonica
la source
15

J'ai testé sur SQL Server 2014 avec l'héritage CE et je n'ai pas non plus obtenu 9% d'estimation de cardinalité. Je n'ai rien trouvé de précis en ligne, j'ai donc fait des tests et j'ai trouvé un modèle qui convient à tous les cas de test que j'ai essayés, mais je ne peux pas être sûr qu'il est complet.

Dans le modèle que j'ai trouvé, l'estimation est dérivée du nombre de lignes du tableau, de la longueur de clé moyenne des statistiques de la colonne filtrée et parfois de la longueur du type de données de la colonne filtrée. Il existe deux formules différentes utilisées pour l'estimation.

Si FLOOR (longueur de clé moyenne) = 0, la formule d'estimation ignore les statistiques de la colonne et crée une estimation basée sur la longueur du type de données. Je n'ai testé qu'avec VARCHAR (N), il est donc possible qu'il existe une formule différente pour NVARCHAR (N). Voici la formule pour VARCHAR (N):

(estimation des lignes) = (lignes du tableau) * (-0,004869 + 0,032649 * log10 (longueur du type de données))

Cela a un très bon ajustement, mais ce n'est pas parfaitement précis:

premier graphique de formule

L'axe des x est la longueur du type de données et l'axe des y est le nombre de lignes estimées pour une table avec 1 million de lignes.

L'optimiseur de requêtes utiliserait cette formule si vous n'aviez pas de statistiques sur la colonne ou si la colonne a suffisamment de valeurs NULL pour conduire la longueur de clé moyenne en dessous de 1.

Par exemple, supposons que vous disposiez d'une table avec 150 000 lignes avec filtrage sur un VARCHAR (50) et aucune statistique de colonne. La prédiction de l'estimation de ligne est:

150000 * (-0,004869 + 0,032649 * log10 (50)) = 7590,1 lignes

SQL pour le tester:

CREATE TABLE X_CE_LIKE_TEST_1 (
STRING VARCHAR(50)
);

CREATE STATISTICS X_STAT_CE_LIKE_TEST_1 ON X_CE_LIKE_TEST_1 (STRING) WITH NORECOMPUTE;

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_1 WITH (TABLOCK) (STRING)
    SELECT TOP (150000) 'ZZZZZ'
    FROM NUMS
    ORDER BY NUM;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_1
WHERE STRING LIKE @LastName;

SQL Server donne un nombre de lignes estimé à 7242,47, ce qui est un peu proche.

Si FLOOR (longueur de clé moyenne)> = 1, une formule différente est utilisée, basée sur la valeur de FLOOR (longueur de clé moyenne). Voici un tableau de certaines des valeurs que j'ai essayées:

1    1.5%
2    1.5%
3    1.64792%
4    2.07944%
5    2.41416%
6    2.68744%
7    2.91887%
8    3.11916%
9    3.29584%
10   3.45388%

Si FLOOR (longueur de clé moyenne) <6, utilisez le tableau ci-dessus. Sinon, utilisez l'équation suivante:

(estimation des lignes) = (lignes du tableau) * (-0,003381 + 0,034539 * log10 (FLOOR (longueur de clé moyenne)))

Celui-ci a un meilleur ajustement que l'autre, mais il n'est toujours pas parfaitement précis.

deuxième graphique de formule

L'axe des x est la longueur de clé moyenne et l'axe des y est le nombre de lignes estimées pour une table avec 1 million de lignes.

Pour donner un autre exemple, supposons que vous disposiez d'un tableau de 10 000 lignes avec une longueur de clé moyenne de 5,5 pour les statistiques de la colonne filtrée. L'estimation de la ligne serait:

10000 * 0,241416 = 241,416 lignes.

SQL pour le tester:

CREATE TABLE X_CE_LIKE_TEST_2 (
STRING VARCHAR(50)
);

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_2 WITH (TABLOCK) (STRING)
    SELECT TOP (10000) 
    CASE 
      WHEN NUM % 2 = 1 THEN REPLICATE('Z', 5) 
      ELSE REPLICATE('Z', 6)
    END
    FROM NUMS
    ORDER BY NUM;

CREATE STATISTICS X_STAT_CE_LIKE_TEST_2 ON X_CE_LIKE_TEST_2 (STRING) 
WITH NORECOMPUTE, FULLSCAN;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_2
WHERE STRING LIKE @LastName;

L'estimation de la ligne est 241.416, ce qui correspond à ce que vous avez dans la question. Il y aurait une erreur si j'utilisais une valeur qui n'était pas dans le tableau.

Les modèles ici ne sont pas parfaits mais je pense qu'ils illustrent assez bien le comportement général.

Joe Obbish
la source