Modifications des estimations sur les prédicats qui contiennent SUBSTRING () dans SQL Server 2016?

13

Existe-t-il de la documentation ou des recherches sur les modifications dans SQL Server 2016 concernant la façon dont la cardinalité est estimée pour les prédicats contenant SUBSTRING () ou d'autres fonctions de chaîne?

La raison pour laquelle je demande, c'est que je regardais une requête dont les performances se sont dégradées en mode de compatibilité 130 et la raison était liée à un changement d'estimation du nombre de lignes correspondant à une clause WHERE qui contenait un appel à SUBSTRING (). J'ai corrigé le problème avec une réécriture de requête, mais je me demande si quelqu'un est au courant de la documentation sur les modifications dans ce domaine dans SQL Server 2016.

Le code de démonstration est ci-dessous. Les estimations sont très proches dans ce cas de test, mais la précision varie en fonction des données.

Dans le cas de test, dans le niveau de compatibilité 120, SQL Server semble utiliser l'histogramme pour l'estimation, tandis que dans le niveau de compatibilité 130, SQL Server semble supposer que 10% des correspondances de table sont fixes.

CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );

CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);

/* 
Uses fixed % for estimate; 1.1 rows estimated in this case.
    Plan for computation:
        CSelCalcFixedFilter (0.1) <----
            Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/* 
Uses histogram to get estimate of 1
 CSelCalcPointPredsFreqBased <----
      Distinct value calculation:
          CDVCPlanLeaf
              0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
      Individual selectivity calculations:
          (none)
    Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT * 
FROM dbo.StringTest 
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/
James L
la source
1
Vous n'êtes pas sûr de la question particulière, mais si les Y5_EG3chaînes ne sont que des codes et toujours en majuscules, vous pouvez toujours essayer de spécifier un classement binaire - Latin1_General_100_BIN2- ce qui devrait améliorer la vitesse des opérations de filtrage. Ajoutez simplement COLLATE Latin1_General_100_BIN2à la CREATE TABLEdéclaration, juste après le varchar(15). Je serais curieux de voir si cela affectait également la génération / estimation du plan.
Solomon Rutzky
Continuons cette discussion dans le chat .
James L

Réponses:

8

Je ne connais aucune documentation. J'ai examiné la question et fait quelques observations cependant trop longues pour un commentaire.

L'estimation de 10% n'est pas toujours une dégradation. Prenons l'exemple suivant.

TRUNCATE TABLE dbo.StringTest

INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM   master..spt_values v1,
       master..spt_values v2;

et la WHEREclause de votre question.

WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'

Le tableau contient un million de lignes. Tous correspondent au prédicat. Sous le niveau de compatibilité 130, la supposition de 10% donne une estimation de 100 000. En dessous de 120, les rangées estimées sont de 1,03913.

Le comportement 120 utilise l'histogramme mais uniquement pour obtenir le nombre de lignes distinctes. Le vecteur de densité dans mon cas montre 1.039131E-06 et cela est multiplié par la cardinalité de la table pour obtenir le nombre de lignes estimé. Toutes les valeurs sont en fait différentes mais correspondent toutes au prédicat.

Le suivi de l' query_optimizer_estimate_cardinalityévénement étendu montre que sous 130, il existe deux <StatsCollection Name="CStCollFilter"événements différents . Le premier estime 100 000. Le second charge l'histogramme et utilise le CSelCalcPointPredsFreqBased / DistinctCountCalculator pour obtenir l'estimation de 1,04. Ce deuxième résultat semble inutilisé.

Le comportement que vous avez observé n'est pas appliqué de manière cohérente en 130. J'ai ajouté en ORDER BY TheStringm'attendant à ce que ce soit une victoire claire pour l'estimateur 130 car le 120 se débat avec une allocation de mémoire pour une ligne, mais ce changement mineur était suffisant pour ramener les lignes estimées à 1.03913 dans le cas 130 aussi.

L'ajout OPTION (QUERYRULEOFF SelectToFilter)ramène l'estimation du tri à 100 000, mais l'allocation de mémoire n'augmente pas et les estimations sortant du tri sont toujours basées sur les valeurs distinctes de la table.

entrez la description de l'image ici

De même, le réglage du seuil de coût pour le parallélisme afin que la requête obtienne un plan parallèle était suffisant dans le cas 130 pour revenir à l'estimation inférieure. L'ajout QUERYTRACEON 8757entraîne également l'estimation inférieure. Il semble que l'estimation de 10% ne soit conservée que pour les plans triviaux.

Votre proposition de réécriture avec

WHERE TheString LIKE 'ZZ[_]%'

Affiche des estimations bien supérieures aux deux. La sortie pour cela est

  CSelCalcTrieBased

      Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString

Montrant qu'il a utilisé des essais . Plus d'informations à ce sujet se trouvent dans la section des statistiques récapitulatives des chaînes juste au-dessus ici .

Ce n'est cependant pas la même chose que votre requête d'origine. Comme la première instance de _est désormais supposée être toujours le troisième caractère plutôt que d'être trouvée dynamiquement.

Si cette hypothèse est codée en dur dans votre requête d'origine

 WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'

La méthode d'estimation devient CSelCalcHistogramComparison(INTERVAL)et les lignes estimées deviennent précises.

Il est capable de convertir cela en une gamme

WHERE TheString >=  'ZZ_' AND TheString < ???

et utilisez l'histogramme pour estimer le nombre de lignes avec des valeurs dans cette plage.

Cela ne s'applique cependant qu'à l'estimation de la cardinalité. LIKEest préférable car il peut utiliser une recherche de plage lors de l'exécution. SUBSTRING(TheString, 1, 3)ou LEFT(TheString, 3)ne peut pas.

Martin Smith
la source