Die bizarre Dichte führt zu Stichprobenstatistiken

8

Ein NC-Index erhält eine völlig andere statistische Verteilung, wenn er mit Stichproben gegenüber Vollscan geschätzt wird. das abgetastete hat einen bizarren Dichtevektor. Dies führt zu schlechten Ausführungsplänen.


Ich habe eine Tabelle mit ~ 27 Millionen Zeilen mit einer FK-Spalte ungleich Null, die von einem nicht gruppierten Index unterstützt wird. Die Tabelle ist auf ihrem Primärschlüssel gruppiert. Beide Spalten sind varchar.

Ein Fullscan-Statistik-Update für unsere FK-Spalte liefert einen normal aussehenden Dichtevektor:

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

Das heißt, wir werden voraussichtlich etwa 1,7 Zeilen für jede einzelne INSTANCELEMENTIDZeile lesen, mit der wir uns verbinden.

Ein typischer Behälter aus dem Histogramm sieht folgendermaßen aus:

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

Wenn wir jedoch ein Stichproben-Update durchführen (unter Verwendung der Standard-Beispielnummer, die 230.000 Zeilen für diese Tabelle beträgt), wenden sich die Dinge dem Bizarren zu:

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

Die Dichte an INSTANCEELEMENTIDist jetzt zwei Größenordnungen größer. (Die Dichte für beide Spalten wurde jedoch auf einen ziemlich akzeptablen Wert geschätzt).

Ein typischer Behälter aus dem Histogramm sieht jetzt so aus.

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

Das ist eine völlig andere Verteilung. Beachten Sie, dass das INSTANCEELEMENTIDmit der höchsten Anzahl von zugeordneten IDs 12 hat, die häufigste Zahl ist 1. Es ist auch sehr seltsam, dass einige Bins EQ_ROWS = 1 erhalten, dies passiert ungefähr 10% der Bins.

Es gibt kein "unglückliches" Ziehen von seltsamen Reihen, die dazu beitragen könnten.

Lese ich das Histogramm richtig? Sieht es nicht so aus, als hätte die Stichprobe EQ_ROWS, DISTINCT_RANGE_ROWS und AVG_RANGE_ROWS falsch skaliert?

Der Tisch ist, soweit ich das beurteilen kann, ungezähmt. Ich habe versucht, den Sampler zu emulieren, indem ich die Werte selbst mit geschätzt habe tablesample. Wenn Sie diese Ergebnisse auf normale Weise zählen, erhalten Sie Ergebnisse, die mit der Vollscan-Version und nicht mit dem Sampler übereinstimmen.

Außerdem konnte ich dieses Verhalten auf Clustered-Indizes nicht reproduzieren.


Ich habe dies auf Folgendes eingegrenzt, um Folgendes zu reproduzieren:

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

set nocount on

declare @rowlimit integer = 20000000;

Die Tabelle muss ausreichend groß sein, damit dies beobachtet werden kann. Ich habe das mit uniqueidentiferund varchar(100)aber nicht gesehen 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)

Vergleichen Sie die beiden Statistiken. Der mit der Abtastung stellt jetzt einen Vektor mit insgesamt unterschiedlicher Dichte dar und die Histogrammfächer sind ausgeschaltet. Beachten Sie, dass die Tabelle nicht verzerrt ist.

Die Verwendung intals Datentyp verursacht dies nicht. Untersucht SQL Server bei der Verwendung nicht den gesamten Datenpunkt varchar?

Es ist erwähnenswert, dass das Problem zu skalieren scheint und eine Erhöhung der Abtastrate hilft.

Paul White 9
quelle

Antworten:

3

Ich habe das gleiche Dichteproblem bei einigen der nicht gruppierten Indizes in den größten Datenbanken gesehen, auf die ich Zugriff habe. Zunächst beginne ich mit einigen Beobachtungen, die ich zu Histogrammen und Dichteberechnungen gemacht habe:

  • SQL Server kann den Primärschlüssel in der Tabelle verwenden, um etwas über die Dichte beider Spalten abzuleiten. Dies bedeutet, dass die Dichte, die die PK-Spalten enthält, normalerweise sehr genau ist.
  • Die Dichteberechnung für die erste Spalte in der Statistik stimmt mit dem Histogramm überein. Wenn das Histogramm die Daten nicht gut modelliert, ist die Dichte möglicherweise ausgeschaltet.
  • Um das Histogramm zu erstellen, StatManzieht die Funktion Rückschlüsse auf die fehlenden Daten. Das Verhalten kann sich je nach Datentyp der Spalte ändern.

Angenommen, Sie nehmen 100 Zeilen aus einer 10000-Zeilentabelle und erhalten 100 unterschiedliche Werte, um das Problem zu untersuchen. Eine Vermutung für den Rest der Daten in der Tabelle ist, dass es 10000 eindeutige Werte gibt. Eine andere Vermutung ist, dass es 100 verschiedene Werte gibt, aber jeder der Werte 100 Mal wiederholt wird. Die zweite Vermutung mag Ihnen unvernünftig erscheinen, der ich zustimmen werde. Wie balancieren Sie jedoch die beiden Ansätze, wenn die abgetasteten Daten ungleichmäßig verteilt zurückkehren? In der StatManFunktion sind einige von Microsoft dafür entwickelte Algorithmen enthalten . Die Algorithmen funktionieren möglicherweise nicht für alle Datenstörungen und alle Stichprobenstufen.

Lassen Sie uns ein relativ einfaches Beispiel durchgehen. Ich werde VARCHARSpalten wie in Ihrer Tabelle verwenden, um das gleiche Verhalten zu sehen. Ich werde jedoch nur einen verzerrten Wert zur Tabelle hinzufügen. Ich teste gegen SQL Server 2016 SP1. Beginnen Sie mit 100.000 Zeilen mit 100.000 eindeutigen Werten für die FKSpalte:

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;

Hier einige Beispiele aus der Statistik:

╔═════════════╦════════════════╦═════════╗
 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              
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝

Für gleichmäßig verteilte Daten mit einem eindeutigen Wert pro Zeile erhalten wir eine genaue Dichte, selbst mit einer VARCHARHistogrammspalte und einer Stichprobengröße von 14294 Zeilen.

Fügen wir nun einen verzerrten Wert hinzu und aktualisieren die Statistiken erneut:

-- 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;

Bei einer Stichprobengröße von 17010 Zeilen ist die Dichte der ersten Spalte kleiner als sie sein sollte:

╔══════════════╦════════════════╦═════════╗
 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       
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝

Es ist überraschend, dass der AVG_RANGE_ROWSWert für alle Schritte bei etwa 6,9 ziemlich einheitlich ist, selbst für Schlüsseleimer, für die die Stichprobe keine doppelten Werte gefunden haben konnte. Ich weiß nicht warum das so ist. Die wahrscheinlichste Erklärung ist, dass der Algorithmus, mit dem die fehlenden Seiten erraten werden, mit dieser Datenverteilung und Stichprobengröße nicht gut zurechtkommt.

Wie bereits erwähnt, ist es möglich, die Dichte für die FK-Säule unter Verwendung des Histogramms zu berechnen. Die Summe der DISTINCT_RANGE_ROWSWerte für alle Schritte beträgt 14497. Es gibt 179 Histogrammschritte, daher sollte die Dichte etwa 1 / (179 + 14497) = 0,00006813845 betragen, was ziemlich nahe am angegebenen Wert liegt.

Tests mit einer größeren Tabelle können zeigen, wie sich das Problem verschlimmern kann, wenn die Tabelle größer wird. Dieses Mal beginnen wir mit 1 M Zeilen:

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;

Das Statistikobjekt ist noch nicht interessant. Die Dichte für FKist 1.025289E-06, was nahezu exakt ist (1.0E-06).

Fügen wir nun einen verzerrten Wert hinzu und aktualisieren die Statistiken erneut:

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;

Bei einer Stichprobengröße von 45627 Zeilen ist die Dichte der ersten Spalte schlechter als zuvor:

╔══════════════╦════════════════╦═════════╗
 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_ROWSist bis zu 26. Interessanterweise liegt der Durchschnittswert für AVG_RANGE_ROWSwieder bei 6,9 , wenn ich die Stichprobengröße auf 170100 Zeilen ändere (10-fache der anderen Tabelle) . Wenn Ihre Tabelle größer wird, wählt SQL Server eine kleinere Stichprobengröße aus, was bedeutet, dass er einen größeren Prozentsatz der Seiten in der Tabelle erraten muss. Dies kann statistische Probleme für bestimmte Arten von Datenversatz übertreiben.

Abschließend ist zu beachten, dass SQL Server die Dichte nicht wie folgt berechnet:

SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);

Was für einige Datenverteilungen sehr genau sein wird. Stattdessen werden undokumentierte Algorithmen verwendet . In Ihrer Frage haben Sie gesagt, dass Ihre Daten nicht verzerrt sind, aber der INSTANCEELEMENTIDWert mit der höchsten Anzahl zugeordneter IDs hat 12 und die häufigste Anzahl ist 1. Für die Zwecke der von diesen verwendeten Algorithmen Statmankönnte dies verzerrt sein.

An diesem Punkt können Sie nichts dagegen tun, außer Statistiken mit einer höheren Abtastrate zu sammeln. Eine gängige Strategie ist das Sammeln von Statistiken mit FULLSCANund NORECOMPUTE. Sie können die Statistiken mit einem Job in jedem Intervall aktualisieren, das für Ihre Datenänderungsrate sinnvoll ist. Nach meiner Erfahrung ist ein FULLSCANUpdate nicht so schlecht, wie die meisten Leute denken, insbesondere gegen einen Index. SQL Server kann einfach den gesamten Index anstelle der gesamten Tabelle scannen (wie dies bei einer Zeilenspeichertabelle für eine nicht indizierte Spalte der Fall wäre). Darüber hinaus werden in SQL Serer 2014 nur FULLSCANStatistikaktualisierungen parallel durchgeführt, sodass eine FULLSCANAktualisierung schneller abgeschlossen werden kann als einige Stichprobenaktualisierungen.

Joe Obbish
quelle
Danke für die Antwort, Joe! Dies scheint ein Fehler oder eine Funktionslücke zu sein. Denken Sie daran, dass dieses Verhalten nicht auftritt, wenn Sie INT-basierte Werte verwenden. Auf INTs funktioniert das System viel besser und Sie erhalten eine Schätzung der statistischen Verteilung, die sich der realen Verteilung viel besser nähert. Während StatMan offensichtlich einige Glättungen / Heuristiken durchführt; Ich würde sagen, es ist ziemlich beunruhigend, dass Sie selbst viel bessere Ergebnisse tablesample
@JohanBenumEvensberget IMO ist es nicht so unvernünftig, dass es sich für INT-Spalten anders verhält. Mit INT haben Sie eine viel engere Domäne für fehlende Werte. Für Saiten kann es wirklich alles bis zur Längenbeschränkung sein. Es kann beunruhigend sein, wenn wir kein gutes Histogramm erhalten, aber es funktioniert die meiste Zeit recht gut. Da der Code geheim ist, können wir nicht wirklich sagen, ob er wie erwartet funktioniert oder nicht. Sie können hier einen Beitrag verfassen,
Joe Obbish