web-dev-qa-db-fra.com

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

J'avais l'impression que lors de l'utilisation de l'opérateur LIKE 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ête n'a pas à recourir aux suppositions de sélectivité).

Lors de l'exécution de la requête ci-dessous sur 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 "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é d'autre en ligne que cet article d'Itzik Ben-Gan , qui indique "Lors de l'utilisation du prédicat LIKE dans tous les scénarios d'optimisation pour des scénarios inconnus, l'héritage et les nouveaux CE utilisent une estimation de 9%." . Les informations contenues dans ce message semblent incorrectes.

24
Fza

La supposition pour LIKEdans votre cas est basée sur:

  • G: estimation 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::ProbLikeGuess Utilise:

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

Remarques:

  • Le terme LOG(D) est omis si D est compris entre 1 et 2.
  • Si D est inférieur à 1 (y compris pour les statistiques manquantes ou NULL):
    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 de DBCC SHOW_STATISTICS Arrondie vers le bas):

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

Autres exemples de valeurs:

  = Estimation à l'aide de 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;
28
Paul White 9

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 correspond à 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 ajustement très agréable, mais ce n'est pas parfaitement précis:

first formula graph

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 estimé de lignes de 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.

second formula graph

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.

15
Joe Obbish