Des résultats de densité bizarres dans les statistiques échantillonnées

8

Un indice NC obtient une distribution statistique totalement différente lorsqu'il est estimé avec l'échantillonnage par rapport au balayage complet; celui échantillonné ayant un vecteur de densité bizarre. Il en résulte de mauvais plans d'exécution.


J'ai une table de ~ 27M lignes, avec une colonne FK non nulle prise en charge par un index non cluster. La table est regroupée sur sa clé primaire. Les deux colonnes sont varchar.

Une mise à jour des statistiques de balayage complet pour notre colonne FK donne un vecteur de densité d'aspect normal:

All density Average Length  Columns
6,181983E-08    45,99747    INSTANCEELEMENTID
3,615442E-08    95,26874    INSTANCEELEMENTID, ID

Autrement dit, nous devons lire environ 1,7 lignes pour chaque élément distinct INSTANCELEMENTIDauquel nous nous joignons.

Un bac typique de l'histogramme ressemble à ceci:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          133053      10      71366               1,679318

Cependant, si nous faisons une mise à jour échantillonnée (en utilisant le numéro d'échantillon par défaut qui est 230k lignes pour ce tableau), les choses tournent au bizarre:

4,773657E-06    45,99596    INSTANCEELEMENTID
3,702179E-08    95,30183    INSTANCEELEMENTID, ID

La densité INSTANCEELEMENTIDest désormais supérieure de deux ordres de grandeur . (La densité des deux colonnes a cependant été estimée à une valeur tout à fait acceptable).

Un casier typique de l'histogramme ressemble maintenant à ceci;

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS     DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          143870,4    766,2573    1247                115,3596
ZOTZOT          131560,7    1           969                 135,7092

qui est une distribution complètement différente. Notez que le INSTANCEELEMENTIDplus grand nombre de IDs associés a 12, le nombre le plus courant est 1. Il est également très étrange que certains bacs obtiennent EQ_ROWS = 1, cela arrive à environ 10% des bacs.

Il n'y a pas de tirage «malchanceux» de rangées étranges qui pourrait y contribuer.

Suis-je en train de lire correctement l'histogramme? Ne semble-t-il pas que l'échantillonnage a en quelque sorte mis à l'échelle EQ_ROWS, DISTINCT_RANGE_ROWS et AVG_RANGE_ROWS de manière incorrecte?

Pour autant que je sache, la table n'est pas qualifiée. J'ai essayé d'émuler l'échantillonneur en estimant moi-même les valeurs tablesample. Compter ces résultats de manière normale donne des résultats qui correspondent à la version fullscan, pas à l'échantillonneur.

De plus, je n'ai pas pu reproduire ce comportement sur des index clusterisés.


J'ai réduit cela à ceci pour reproduire:

CREATE TABLE F_VAL (
    id varchar(100) primary key,
    num_l_val int not null
)

set nocount on

declare @rowlimit integer = 20000000;

Le tableau doit être suffisamment grand pour que cela soit observé. Je l' ai vu avec uniqueidentiferet varchar(100)non int.

declare @i integer = 1;

declare @r float = rand()

while @i < @rowlimit
begin
set @r = rand()
insert f_val (id,num_l_val)
values (
   cast(@i as varchar(100)) + REPLICATE('f', 40 - len(@i)),
   case when @r > 0.8 then 4 when @r > 0.5 then 3 when @r > 0.4 then 2 else 1 end
)
  set @i = @i + 1

end

create table k_val (
 id int identity primary key,
 f_val varchar(100) not null,
)

insert into k_val(f_val)
select id from F_VAL
union all select id from f_val where num_l_val - 1 = 1
union all select id from f_val where num_l_val - 2 = 1
union all select id from f_val where num_l_val - 3 = 1
order by id

create nonclustered index IX_K_VAL_F_VAL  ON K_VAL (F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) 
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) WITH FULLSCAN
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

Comparez les deux statistiques; celui avec échantillonnage représente maintenant un vecteur de densité totalement différent et les cases d'histogramme sont désactivées. Notez que le tableau n'est pas asymétrique.

L'utilisation en inttant que type de données ne provoque pas cela, SQL Server n'examine-t-il pas l'intégralité du point de données lors de l'utilisation varchar?

Il vaut la peine de mentionner que le problème semble s'aggraver, l'augmentation de la fréquence d'échantillonnage aide.

Paul White 9
la source

Réponses:

3

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 StatManfonction 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 StatManfonction. 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 VARCHARcolonnes 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 FKcolonne:

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 VARCHARcolonne 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_ROWSsoit 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_ROWSvaleurs 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 FKest 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_ROWSest 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_ROWSest à 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 INSTANCEELEMENTIDvaleur avec le plus grand nombre d'ID associés a 12 et le nombre le plus commun est 1. Pour les algorithmes utilisés, Statmancela 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 FULLSCANet 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 FULLSCANmise à 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 FULLSCANmises à jour des statistiques sont effectuées en parallèle, de sorte qu'une FULLSCANmise à jour peut se terminer plus rapidement que certaines mises à jour échantillonnées.

Joe Obbish
la source
Merci pour la réponse, Joe! Cela semble être un bug ou une lacune dans les fonctionnalités; rappelez-vous que ce problème ne se produit pas lorsque vous utilisez des valeurs basées sur INT. Sur les INT, le système fonctionne beaucoup mieux et vous obtenez une estimation de la distribution statistique qui s'approche beaucoup mieux de la distribution réelle. Alors que StatMan fait évidemment du lissage / heuristique; Je dirais que c'est assez déconcertant que vous puissiez obtenir de bien meilleurs résultats vous-même en calculant l'histogramme directement, en utilisant toujours les mêmes données sources que celles que vous obtiendriez avectablesample
@JohanBenumEvensberget IMO ce n'est pas si déraisonnable qu'il se comporte différemment pour les colonnes INT. Avec INT, vous disposez d'un domaine beaucoup plus limité pour les valeurs manquantes. Pour les cordes, cela peut vraiment aller jusqu'à la limite de longueur. Cela peut être déconcertant lorsque nous n'obtenons pas un bon histogramme, mais cela fonctionne très bien la plupart du temps. Puisque le code est secret, nous ne pouvons pas vraiment dire s'il fonctionne comme prévu ou non. Vous pouvez envisager de publier un article ici si vous pensez que ce problème devrait être résolu par MS: connect.microsoft.com/SQLServer/Feedback
Joe Obbish