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);
*/
Y5_EG3
chaî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 simplementCOLLATE Latin1_General_100_BIN2
à laCREATE TABLE
déclaration, juste après levarchar(15)
. Je serais curieux de voir si cela affectait également la génération / estimation du plan.Réponses:
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.
et la
WHERE
clause de votre question.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 TheString
m'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.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 8757
entraî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
Affiche des estimations bien supérieures aux deux. La sortie pour cela est
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
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
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é.
LIKE
est préférable car il peut utiliser une recherche de plage lors de l'exécution.SUBSTRING(TheString, 1, 3)
ouLEFT(TheString, 3)
ne peut pas.la source