J'ai du mal à comprendre une estimation de cardinalité. Voici ma configuration de test:
J'ai ce proc:
USE StackOverflow2010;
GO
CREATE OR ALTER PROCEDURE #sp_PostsByCommentCount
@CommentCount int
AS
BEGIN
SELECT *
FROM dbo.Posts p
WHERE
p.CommentCount = @CommentCount
OPTION (RECOMPILE);
END;
GO
Il n'y a pas d'index ou de statistiques non cluster sur la table dbo.Posts
(Il existe un index cluster sur Id
).
Lorsque vous demandez un plan estimé pour cela, les "lignes estimées" sortant de dbo.Posts
Sont de 1 934,99:
EXEC #sp_PostsByCommentCount @CommentCount = 51;
L'objet de statistiques suivant a été créé automatiquement lorsque j'ai demandé le plan estimé:
DBCC SHOW_STATISTICS('dbo.Posts', [_WA_Sys_00000006_0519C6AF]);
Les faits saillants sont les suivants:
0.03030303
(33 valeurs distinctes ont été échantillonnées)RANGE_HI_KEY
Dans l'histogramme est 50, avec EQ_ROWS
De 1Le dépassement de toute valeur supérieure à 50 (jusqu'à 2 147 483 647 inclus) entraîne l'estimation de la ligne 1 934,99. Quel calcul ou valeur est utilisée pour produire cette estimation? L'estimateur de cardinalité hérité produit une estimation d'une ligne, soit dit en passant.
Voici quelques théories que j'ai eues, des choses que j'ai essayées ou des informations supplémentaires que j'ai pu creuser en examinant cela.
J'ai d'abord pensé que ce serait le vecteur de densité, le même que si j'avais utilisé OPTION (OPTIMIZE FOR UNKNOWN)
. Mais le vecteur de densité pour cet objet stats est 3,744,192 * 0,03030303 = 113,460, donc ce n'est pas ça.
J'ai essayé de lancer une session Extended Event qui a collecté l'événement query_optimizer_estimate_cardinality
(Dont j'ai entendu parler dans le billet de blog de Paul White Cardinality Estimation: Combining Density Statistics ), et j'ai obtenu ce genre d'informations intéressantes:
<CalculatorList>
<FilterCalculator CalculatorName="CSelCalcColumnInInterval" Selectivity="-1.000"
CalculatorFailed="true" TableName="[p]" ColumnName="CommentCount" />
<FilterCalculator CalculatorName="CSelCalcAscendingKeyFilter" Selectivity="0.001"
TableName="[p]" ColumnName="CommentCount" UseAverageFrequency="true"
StatId="4" />
</CalculatorList>
Il semble donc que la calculatrice CSelCalcAscendingKeyFilter
ait été utilisée (l'autre dit qu'elle a échoué, quoi que cela signifie). Cette colonne n'est pas une clé, ni unique, ni nécessairement ascendante, mais peu importe.
Faire une recherche sur Google de ce terme m'a conduit à quelques articles de blog:
Ces messages indiquent que le nouveau CE fonde ces estimations hors de l'histogramme sur une combinaison du vecteur de densité et du compteur de modification de la statistique. Malheureusement, j'ai déjà exclu le vecteur de densité (je pense?!), Et le compteur de modifications est nul (par sys.dm_db_stats_properties
De toute façon).
Forrest m'a suggéré d'activer TF 2363 pour obtenir plus d'informations sur le processus d'estimation. Je pense que la chose la plus pertinente de cette sortie est la suivante:
Plan for computation:
CSelCalcAscendingKeyFilter(avg. freq., QCOL: [p].CommentCount)
Selectivity: 0.000516798
Il s'agit d'une percée (merci, Forrest!): Ce nombre 0.000516798
(Qui semble avoir été arrondi de manière inutile dans l'attribut XE Selectivity="0.001"
Ci-dessus) multiplié par le nombre de lignes du tableau est l'estimation que je cherchais (1 934,99).
Il me manque probablement quelque chose d'évident, mais je n'ai pas été en mesure de rétroconcevoir comment cette valeur de sélectivité est produite dans la calculatrice CSelCalcAscendingKeyFilter
.
D'après mes tests, l'estimation de la cardinalité hors limites est simplement la racine carrée du nombre de lignes, limitée en dessous par le nombre de lignes ajoutées depuis la dernière mise à jour des statistiques, et limitée au-dessus par la moyenne des lignes par valeur.
Dans votre cas, 1934,99 = SQRT (3744192)
Configuration de test ci-dessous:
--setup
USE TestDB
ALTER DATABASE [TestDB] SET AUTO_UPDATE_STATISTICS OFF
GO
DROP TABLE IF EXISTS dbo.Hist
CREATE TABLE dbo.Hist (
ID int identity primary key,
Num int
)
INSERT dbo.Hist
SELECT TOP 300
(ROW_NUMBER() OVER(ORDER BY(SELECT 1/0)))%3
FROM master..spt_values a
CROSS JOIN master..spt_values b
--Get estimated plan
--don't forget to run right after setup to auto-create stats
SELECT *
FROM dbo.Hist
WHERE Num = 1000
--gradually add rows, then rerun estimate above
INSERT dbo.Hist
SELECT TOP 100
-1
FROM master..spt_values a
--I sure hope you weren't testing this in prod (cleanup)
ALTER DATABASE [TestDB] SET AUTO_UPDATE_STATISTICS ON
GO
Étonnamment, même des estimations de lignes ont été générées à partir de cette approche: 20 à 400 lignes au total, 30 à 900, 40 à 1600, etc.
Au-delà de 10000 cependant, l'estimation des lignes atteint un maximum de 100, ce qui correspond au nombre de lignes par valeur dans les statistiques existantes. L'ajout de seulement 10 lignes définira l'estimation à 10, car sqrt (300)> 10.
Ainsi, les estimations pourraient être exprimées en utilisant cette formule:
Estimate = MIN(SQRT(AC), MIN(AR, MC))
Notez que si des statistiques sont échantillonnées, alors MC n'est pas pris en compte. La formule devient donc:
Estimate = MIN(SQRT(AC), AR))
Où
Les formules de ces estimations et d'autres détails sur la calculatrice se trouvent dans cet article de blog: Analyse des estimations de la calculatrice CSelCalcAscendingKeyFilter