J'ai une table avec une colonne de chaîne et un prédicat qui vérifie les lignes avec une certaine longueur. Dans SQL Server 2014, je vois une estimation de 1 ligne quelle que soit la longueur que je vérifie. Cela donne des plans très médiocres car il existe actuellement des milliers, voire des millions de lignes et SQL Server choisit de mettre cette table sur le côté extérieur d'une boucle imbriquée.
Existe-t-il une explication pour l'estimation de la cardinalité de 1.0003 pour SQL Server 2014 tandis que SQL Server 2012 estime 31 622 lignes? Y a-t-il une bonne solution de contournement?
Voici une courte reproduction du problème:
-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO
INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
SELECT TOP 1000000
CONVERT(VARCHAR(10),
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
FROM master..spt_values v1
CROSS JOIN master..spt_values v2
GO
-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO
Voici un script plus complet montrant des tests supplémentaires
J'ai également lu le Blagwaet sur l'estimateur de cardinalité SQL Server 2014 , mais n'a rien trouvé là-bas qui a clarifié la situation.
Pour le héritage CE, je vois que l'estimation est de 3,16228% des lignes - et c'est un "nombre magique" heuristique utilisé pour la colonne = prédicats littéraux (il existe d'autres heuristiques basées sur la construction du prédicat - mais le LEN
Enveloppé autour de la colonne des résultats Legacy CE correspond à ce framework). Vous pouvez voir des exemples de ceci sur un post sur sélectivité devine en absence de statistiques par Joe Sack et estimation de comparaison constante constante par Ian Jose.
-- Legacy CE: 31622.8 rows
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION ( QUERYTRACEON 9481); -- Legacy CE
GO
Maintenant que pour le nouveau comportement CE, il semble que ceci est maintenant visible pour l'optimiseur (ce qui signifie que nous pouvons utiliser des statistiques). J'ai traversé l'exercice de la recherche de la sortie de la calculatrice ci-dessous et vous pouvez examiner la génération automatique associée des statistiques en tant que pointeur:
-- New CE: 1.00007 rows
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION ( QUERYTRACEON 2312 ); -- New CE
GO
-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO
/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
999927
Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
CStCollFilter(ID=2, CARD=1.00007)
CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)
End selectivity computation
*/
EXEC tempdb..sp_helpstats '#customers';
--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change
Malheureusement, la logique repose sur une estimation du nombre de valeurs distinctes, qui n'est pas ajustée pour l'effet de la fonction LEN
.
Vous pouvez obtenir une estimation basée sur Trie sous les deux modèles CE en réécrivant le LEN
comme un LIKE
:
SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);
Informations sur les drapeaux de trace utilisés: