J'ai vu ce même problème de densité sur certains des index non cluster sur les plus grandes bases de données auxquelles j'ai accès. Je commencerai par quelques observations que j'ai faites sur les histogrammes et les calculs de densité:
- SQL Server peut utiliser la clé primaire de la table pour déduire quelque chose sur la densité des deux colonnes. Cela signifie que la densité qui inclut les colonnes PK sera généralement très précise.
- Le calcul de la densité pour la première colonne des statistiques est cohérent avec l'histogramme. Si l'histogramme ne modélise pas bien les données, la densité peut être désactivée.
- Pour créer l'histogramme, la
StatMan
fonction fait des inférences sur les données manquantes. Le comportement peut changer en fonction du type de données de la colonne.
Pour une façon d'examiner le problème, supposons que vous échantillonnez 100 lignes d'une table de 10000 lignes et que vous obtenez 100 valeurs distinctes. On devine ce que le reste des données dans le tableau est qu'il y a 10000 valeurs uniques. Une autre supposition est qu'il existe 100 valeurs distinctes mais chacune des valeurs est répétée 100 fois. La deuxième supposition peut vous sembler déraisonnable, avec laquelle je serai d'accord. Cependant, comment équilibrez-vous les deux approches lorsque les données échantillonnées reviennent inégalement réparties? Il y a un ensemble d'algorithmes développés pour cela par Microsoft contenus dans la StatMan
fonction. Les algorithmes peuvent ne pas fonctionner pour toutes les interruptions de données et tous les niveaux d'échantillonnage.
Passons en revue un exemple relativement simple. Je vais utiliser des VARCHAR
colonnes comme dans votre tableau pour voir certains du même comportement. Cependant, je vais simplement ajouter une valeur asymétrique à la table. Je teste contre SQL Server 2016 SP1. Commencez avec 100 000 lignes avec 100 000 valeurs uniques pour la FK
colonne:
DROP TABLE IF EXISTS X_STATS_SMALL;
CREATE TABLE X_STATS_SMALL (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID)
);
-- insert 100k rows
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.GetNums(100000);
CREATE INDEX IX_X_STATS_SMALL ON X_STATS_SMALL (FK);
-- get sampled stats
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;
Voici quelques exemples des statistiques:
╔═════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠═════════════╬════════════════╬═════════╣
║ 1.00001E-05 ║ 4.888205 ║ FK ║
║ 1.00001E-05 ║ 9.77641 ║ FK, ID ║
╚═════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
║ 1005 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 10648 ║ 665.0898 ║ 1 ║ 664 ║ 1.002173 ║
║ 10968 ║ 431.6008 ║ 1 ║ 432 ║ 1 ║
║ 11182 ║ 290.0924 ║ 1 ║ 290 ║ 1 ║
║ 1207 ║ 445.7517 ║ 1 ║ 446 ║ 1 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 99989 ║ 318.3941 ║ 1 ║ 318 ║ 1 ║
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝
Pour des données uniformément réparties avec une valeur unique par ligne, nous obtenons une densité précise, même avec une VARCHAR
colonne d'histogramme et une taille d'échantillon de 14294 lignes.
Ajoutons maintenant une valeur asymétrique et mettons à jour les statistiques à nouveau:
-- add 70k rows with a FK value of '35000'
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N + 100000 , '35000', REPLICATE('Z', 900)
FROM dbo.GetNums(70000);
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;
Avec un échantillon de 17010 lignes, la densité de la première colonne est plus petite qu'elle ne devrait l'être:
╔══════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠══════════════╬════════════════╬═════════╣
║ 6.811061E-05 ║ 4.935802 ║ FK ║
║ 5.882353E-06 ║ 10.28007 ║ FK, ID ║
╚══════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦══════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬══════════╬═════════════════════╬════════════════╣
║ 10039 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 10978 ║ 956.9945 ║ 1 ║ 138 ║ 6.954391 ║
║ 11472 ║ 621.0283 ║ 1 ║ 89 ║ 6.941863 ║
║ 1179 ║ 315.6046 ║ 1 ║ 46 ║ 6.907561 ║
║ 11909 ║ 91.62713 ║ 1 ║ 14 ║ 6.74198 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 35000 ║ 376.6893 ║ 69195.05 ║ 54 ║ 6.918834 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 99966 ║ 325.7854 ║ 1 ║ 47 ║ 6.909731 ║
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝
Il est surprenant que le AVG_RANGE_ROWS
soit assez uniforme pour toutes les étapes aux alentours de 6,9, même pour les groupes de clés pour lesquels l'échantillon n'a pas pu trouver de valeurs en double. Je ne sais pas pourquoi c'est. L'explication la plus probable est que l'algorithme utilisé pour deviner les pages manquantes ne convient pas bien à cette distribution de données et à la taille de l'échantillon.
Comme indiqué précédemment, il est possible de calculer la densité de la colonne FK à l'aide de l'histogramme. La somme des DISTINCT_RANGE_ROWS
valeurs pour toutes les étapes est 14497. Il y a 179 étapes d'histogramme, donc la densité devrait être d'environ 1 / (179 + 14497) = 0,00006813845, ce qui est assez proche de la valeur indiquée.
Les tests avec une table plus grande peuvent montrer comment le problème peut s'aggraver à mesure que la table s'agrandit. Cette fois, nous allons commencer avec 1 M de lignes:
DROP TABLE IF EXISTS X_STATS_LARGE;
CREATE TABLE X_STATS_LARGE (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID));
INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.Getnums(1000000);
CREATE INDEX IX_X_STATS_LARGE ON X_STATS_LARGE (FK);
-- get sampled stats
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;
L'objet statistique n'est pas encore intéressant. La densité de FK
est 1.025289E-06 qui est proche de l'exacte (1.0E-06).
Ajoutons maintenant une valeur asymétrique et mettons à jour les statistiques à nouveau:
INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N + 1000000 , '350000', REPLICATE('Z', 900)
FROM dbo.Getnums(700000);
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;
Avec un échantillon de 45627 lignes, la densité de la première colonne est pire qu'auparavant:
╔══════════════╦════════════════╦═════════╗
║ All density ║ Average Length ║ Columns ║
╠══════════════╬════════════════╬═════════╣
║ 2.60051E-05 ║ 5.93563 ║ FK ║
║ 5.932542E-07 ║ 12.28485 ║ FK, ID ║
╚══════════════╩════════════════╩═════════╝
╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
║ RANGE_HI_KEY ║ RANGE_ROWS ║ EQ_ROWS ║ DISTINCT_RANGE_ROWS ║ AVG_RANGE_ROWS ║
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
║ 100023 ║ 0 ║ 1 ║ 0 ║ 1 ║
║ 107142 ║ 8008.354 ║ 1 ║ 306 ║ 26.17787 ║
║ 110529 ║ 4361.357 ║ 1 ║ 168 ║ 26.02392 ║
║ 114558 ║ 3722.193 ║ 1 ║ 143 ║ 26.01217 ║
║ 116696 ║ 2556.658 ║ 1 ║ 98 ║ 25.97568 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 350000 ║ 5000.522 ║ 700435 ║ 192 ║ 26.03268 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 999956 ║ 2406.266 ║ 1 ║ 93 ║ 25.96841 ║
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝
AVG_RANGE_ROWS
est jusqu'à 26. Fait intéressant, si je change la taille de l'échantillon à 170100 lignes (10X l'autre table), la valeur moyenne de AVG_RANGE_ROWS
est à nouveau juste autour de 6,9. Au fur et à mesure que votre tableau s'agrandit, SQL Server choisira un échantillon de plus petite taille, ce qui signifie qu'il doit deviner un pourcentage plus élevé de pages dans le tableau. Cela peut exagérer les problèmes de statistiques pour certains types de biais de données.
En conclusion, il est important de se rappeler que SQL Server ne calcule pas la densité comme ceci:
SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);
Ce qui, pour certaines distributions de données, sera très précis. Au lieu de cela, il utilise des algorithmes non documentés . Dans votre question, vous avez dit que vos données n'étaient pas faussées, mais la INSTANCEELEMENTID
valeur avec le plus grand nombre d'ID associés a 12 et le nombre le plus commun est 1. Pour les algorithmes utilisés, Statman
cela pourrait être faussé.
À ce stade, vous ne pouvez rien y faire, sauf pour collecter des statistiques avec un taux d'échantillonnage plus élevé. Une stratégie courante consiste à collecter des statistiques avec FULLSCAN
et NORECOMPUTE
. Vous pouvez actualiser les statistiques avec un travail à n'importe quel intervalle qui convient à votre taux de modification des données. D'après mon expérience, une FULLSCAN
mise à jour n'est pas aussi mauvaise que la plupart des gens le pensent, en particulier par rapport à un index. SQL Server peut simplement analyser l'intégralité de l'index au lieu de la table entière (comme il le ferait pour une table rowstore par rapport à une colonne non indexée). De plus, dans SQL Serer 2014, seules les FULLSCAN
mises à jour des statistiques sont effectuées en parallèle, de sorte qu'une FULLSCAN
mise à jour peut se terminer plus rapidement que certaines mises à jour échantillonnées.
tablesample