Ich werde diesen Beitrag auf die Erörterung einspaltiger Statistiken beschränken, da er bereits ziemlich langwierig ist und Sie daran interessiert sind, wie SQL Server die Daten in Histogrammschritte unterteilt. Bei mehrspaltigen Statistiken wird das Histogramm nur in der führenden Spalte erstellt.
Wenn SQL Server feststellt, dass eine Statistikaktualisierung erforderlich ist, wird eine versteckte Abfrage gestartet, die entweder alle Daten einer Tabelle oder ein Beispiel der Daten der Tabelle liest. Sie können diese Abfragen mit erweiterten Ereignissen anzeigen. In StatMan
SQL Server wird eine Funktion aufgerufen , die an der Erstellung der Histogramme beteiligt ist. Für einfache Statistikobjekte gibt es mindestens zwei verschiedene Arten von StatMan
Abfragen (es gibt verschiedene Abfragen für schnelle Statistikaktualisierungen, und ich vermute, dass die Funktion für inkrementelle Statistiken in partitionierten Tabellen auch eine andere Abfrage verwendet).
Der erste erfasst einfach alle Daten aus der Tabelle ohne Filterung. Sie können dies sehen, wenn die Tabelle sehr klein ist oder Sie Statistiken mit der FULLSCAN
Option sammeln :
CREATE TABLE X_SHOW_ME_STATMAN (N INT);
CREATE STATISTICS X_STAT_X_SHOW_ME_STATMAN ON X_SHOW_ME_STATMAN (N);
-- after gathering stats with 1 row in table
SELECT StatMan([SC0]) FROM
(
SELECT TOP 100 PERCENT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] WITH (READUNCOMMITTED)
ORDER BY [SC0]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 16);
SQL Server wählt die automatische Stichprobengröße basierend auf der Größe der Tabelle aus (ich denke, dass dies sowohl die Anzahl der Zeilen als auch der Seiten in der Tabelle ist). Wenn eine Tabelle zu groß ist, fällt die automatische Stichprobengröße unter 100%. Folgendes habe ich für dieselbe Tabelle mit 1 Million Zeilen erhalten:
-- after gathering stats with 1 M rows in table
SELECT StatMan([SC0], [SB0000]) FROM
(
SELECT TOP 100 PERCENT [SC0], step_direction([SC0]) over (order by NULL) AS [SB0000]
FROM
(
SELECT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] TABLESAMPLE SYSTEM (6.666667e+001 PERCENT) WITH (READUNCOMMITTED)
) AS _MS_UPDSTATS_TBL_HELPER
ORDER BY [SC0], [SB0000]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1);
TABLESAMPLE
ist dokumentiert , StatMan und step_direction jedoch nicht. Hier tastet SQL Server etwa 66,6% der Daten aus der Tabelle ab, um das Histogramm zu erstellen. Dies bedeutet, dass Sie möglicherweise eine andere Anzahl von Histogrammschritten erhalten, wenn Sie Statistiken (ohne FULLSCAN
) für dieselben Daten aktualisieren . Ich habe das in der Praxis nie beobachtet, aber ich verstehe nicht, warum es nicht möglich wäre.
Lassen Sie uns einige Tests mit einfachen Daten durchführen, um zu sehen, wie sich die Statistiken im Laufe der Zeit ändern. Im Folgenden finden Sie einen Testcode, den ich geschrieben habe, um sequentielle Ganzzahlen in eine Tabelle einzufügen, Statistiken nach jeder Einfügung zu sammeln und Informationen zu den Statistiken in einer Ergebnistabelle zu speichern. Beginnen wir mit dem Einfügen von jeweils 1 Zeile bis zu 10000. Prüfstand:
DECLARE
@stats_id INT,
@table_object_id INT,
@rows_per_loop INT = 1,
@num_of_loops INT = 10000,
@loop_num INT;
BEGIN
SET NOCOUNT ON;
TRUNCATE TABLE X_STATS_RESULTS;
SET @table_object_id = OBJECT_ID ('X_SEQ_NUM');
SELECT @stats_id = stats_id FROM sys.stats
WHERE OBJECT_ID = @table_object_id
AND name = 'X_STATS_SEQ_INT_FULL';
SET @loop_num = 0;
WHILE @loop_num < @num_of_loops
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT @rows_per_loop * (@loop_num - 1) + N FROM dbo.GetNums(@rows_per_loop);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN; -- can comment out FULLSCAN as needed
INSERT INTO X_STATS_RESULTS WITH (TABLOCK)
SELECT 'X_STATS_SEQ_INT_FULL', @rows_per_loop * @loop_num, rows_sampled, steps
FROM sys.dm_db_stats_properties(@table_object_id, @stats_id);
END;
END;
Für diese Daten steigt die Anzahl der Histogrammschritte schnell auf 200 (zuerst wird die maximale Anzahl von Schritten mit 397 Zeilen erreicht), bleibt bei 199 oder 200, bis 1485 Zeilen in der Tabelle enthalten sind, und nimmt dann langsam ab, bis das Histogramm nur noch 3 oder 4 enthält Schritte. Hier ist eine Grafik aller Daten:
So sieht das Histogramm für 10.000 Zeilen aus:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 1 0 1
9999 9997 1 9997 1
10000 0 1 0 1
Ist es ein Problem, dass das Histogramm nur 3 Schritte hat? Es sieht so aus, als ob Informationen aus unserer Sicht erhalten bleiben. Da der Datentyp ein INTEGER ist, können wir herausfinden, wie viele Zeilen in der Tabelle für jede Ganzzahl von 1 bis 10000 enthalten sind. In der Regel kann SQL Server dies auch herausfinden, obwohl dies in einigen Fällen nicht ganz funktioniert . In diesem SE-Beitrag finden Sie ein Beispiel dafür.
Was wird Ihrer Meinung nach passieren, wenn wir eine einzelne Zeile aus der Tabelle löschen und die Statistiken aktualisieren? Idealerweise erhalten wir einen weiteren Histogrammschritt, um zu zeigen, dass die fehlende Ganzzahl nicht mehr in der Tabelle enthalten ist.
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
Das ist ein bisschen enttäuschend. Wenn wir ein Histogramm von Hand erstellen würden, würden wir für jeden fehlenden Wert einen Schritt hinzufügen. SQL Server verwendet einen Allzweckalgorithmus, sodass wir für einige Datensätze möglicherweise ein geeigneteres Histogramm als den von ihm verwendeten Code erstellen können. Natürlich ist der praktische Unterschied zwischen 0 oder 1 Zeile aus einer Tabelle sehr gering. Ich erhalte die gleichen Ergebnisse beim Testen mit 20000 Zeilen, wobei jede Ganzzahl 2 Zeilen in der Tabelle enthält. Das Histogramm erhält keine Schritte, wenn ich Daten lösche.
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 2 0 1
9999 19994 2 9997 2
10000 0 2 0 1
Wenn ich mit 1 Million Zeilen teste, wobei jede Ganzzahl 100 Zeilen in der Tabelle enthält, erhalte ich etwas bessere Ergebnisse, kann aber dennoch von Hand ein besseres Histogramm erstellen.
truncate table X_SEQ_NUM;
BEGIN TRANSACTION;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT N FROM dbo.GetNums(10000);
GO 100
COMMIT TRANSACTION;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- 4 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- now 5 steps with a RANGE_HI_KEY of 998 (?)
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 5 steps
Endgültiges Histogramm:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 100 0 1
998 99600 100 996 100
3983 298100 100 2981 100
9999 600900 100 6009 100
10000 0 100 0 1
Lassen Sie uns weiter mit sequentiellen Ganzzahlen, aber mit mehr Zeilen in der Tabelle testen. Beachten Sie, dass die manuelle Angabe einer Stichprobengröße für zu kleine Tabellen keine Auswirkung hat. Daher füge ich jeder Einfügung 100 Zeilen hinzu und sammle jedes Mal Statistiken mit bis zu 1 Million Zeilen. Ich sehe ein ähnliches Muster wie zuvor, außer wenn ich 637300 Zeilen in der Tabelle erreicht habe, werden nicht mehr 100% der Zeilen in der Tabelle mit der Standardabtastrate abgetastet. Wenn ich Zeilen gewinne, nimmt die Anzahl der Histogrammschritte zu. Möglicherweise liegt dies daran, dass SQL Server mit zunehmender Anzahl nicht abgetasteter Zeilen in der Tabelle mehr Lücken in den Daten aufweist. Ich treffe selbst bei 1 M Reihen nicht 200 Schritte, aber wenn ich weiterhin Reihen hinzufüge, erwarte ich, dass ich dort ankomme und irgendwann wieder nach unten gehe.
Die X-Achse ist die Anzahl der Zeilen in der Tabelle. Wenn die Anzahl der Zeilen zunimmt, variieren die abgetasteten Zeilen ein wenig und überschreiten nicht 650.000.
Lassen Sie uns nun einige einfache Tests mit VARCHAR-Daten durchführen.
CREATE TABLE X_SEQ_STR (X_STR VARCHAR(5));
CREATE STATISTICS X_SEQ_STR ON X_SEQ_STR(X_STR);
Hier füge ich 200 Zahlen (als Zeichenfolgen) zusammen mit NULL ein.
INSERT INTO X_SEQ_STR
SELECT N FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 111 steps, RANGE_ROWS is 0 or 1 for all steps
Beachten Sie, dass NULL immer einen eigenen Histogrammschritt erhält, wenn er in der Tabelle gefunden wird. SQL Server hätte mir genau 201 Schritte geben können, um alle Informationen zu erhalten, aber das hat es nicht getan. Technisch gesehen gehen Informationen verloren, weil '1111' beispielsweise zwischen '1' und '2' sortiert.
Versuchen wir nun, verschiedene Zeichen anstelle von ganzen Zahlen einzufügen:
truncate table X_SEQ_STR;
INSERT INTO X_SEQ_STR
SELECT CHAR(10 + N) FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 95 steps, RANGE_ROWS is 0 or 1 or 2
Kein wirklicher Unterschied zum letzten Test.
Versuchen wir nun, Zeichen einzufügen, aber unterschiedliche Nummern für jedes Zeichen in die Tabelle aufzunehmen. Hat zum Beispiel CHAR(11)
1 Zeile, CHAR(12)
hat 2 Zeilen usw.
truncate table X_SEQ_STR;
DECLARE
@loop_num INT;
BEGIN
SET NOCOUNT ON;
SET @loop_num = 0;
WHILE @loop_num < 200
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_STR WITH (TABLOCK)
SELECT CHAR(10 + @loop_num) FROM dbo.GetNums(@loop_num);
END;
END;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 148 steps, most with RANGE_ROWS of 0
Nach wie vor bekomme ich immer noch nicht genau 200 Histogrammschritte. Viele der Schritte haben jedoch RANGE_ROWS
0.
Für den letzten Test werde ich eine zufällige Zeichenfolge mit 5 Zeichen in jede Schleife einfügen und jedes Mal Statistiken sammeln. Hier ist der Code der zufälligen Zeichenfolge:
char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))
Hier ist das Diagramm der Zeilen in Tabellen- und Histogrammschritten:
Beachten Sie, dass die Anzahl der Schritte nicht unter 100 sinkt, sobald sie auf und ab gehen. Ich habe von irgendwoher gehört (kann es aber momentan nicht beziehen), dass der SQL Server-Algorithmus zum Erstellen von Histogrammen Histogrammschritte kombiniert, da kein Platz mehr für sie vorhanden ist. Sie können also drastische Änderungen in der Anzahl der Schritte vornehmen, indem Sie nur ein paar Daten hinzufügen. Hier ist ein Beispiel der Daten, die ich interessant fand:
ROWS_IN_TABLE ROWS_SAMPLED STEPS
36661 36661 133
36662 36662 143
36663 36663 143
36664 36664 141
36665 36665 138
Selbst beim Abtasten mit FULLSCAN
kann das Hinzufügen einer einzelnen Zeile die Anzahl der Schritte um 10 erhöhen, konstant halten, dann um 2 verringern und dann um 3 verringern.
Was können wir daraus zusammenfassen? Ich kann nichts davon beweisen, aber diese Beobachtungen scheinen zuzutreffen:
- SQL Server verwendet einen allgemeinen Algorithmus zum Erstellen der Histogramme. Bei einigen Datenverteilungen kann es möglich sein, eine vollständigere Darstellung der Daten von Hand zu erstellen.
- Wenn die Tabelle NULL-Daten enthält und die Statistikabfrage diese findet, erhalten diese NULL-Daten immer einen eigenen Histogrammschritt.
- Der in der Tabelle gefundene Mindestwert erhält einen eigenen Histogrammschritt mit
RANGE_ROWS
= 0.
- Der in der Tabelle gefundene Maximalwert ist der endgültige Wert
RANGE_HI_KEY
in der Tabelle.
- Wenn SQL Server mehr Daten abtastet, müssen möglicherweise vorhandene Schritte kombiniert werden, um Platz für die neu gefundenen Daten zu schaffen. Wenn Sie sich genügend Histogramme ansehen, werden möglicherweise allgemeine Werte für
DISTINCT_RANGE_ROWS
oder wiederholt RANGE_ROWS
. Zum Beispiel wird 255 einige Male für RANGE_ROWS
und DISTINCT_RANGE_ROWS
für den endgültigen Testfall hier angezeigt.
- Bei einfachen Datenverteilungen kombiniert SQL Server möglicherweise sequentielle Daten zu einem Histogrammschritt, der keinen Informationsverlust verursacht. Wenn Sie jedoch Lücken zu den Daten hinzufügen, wird das Histogramm möglicherweise nicht so angepasst, wie Sie es sich erhoffen.
Wann ist das alles ein Problem? Es ist ein Problem, wenn eine Abfrage aufgrund eines Histogramms, das die Datenverteilung nicht so darstellen kann, dass das Abfrageoptimierungsprogramm gute Entscheidungen treffen kann, eine schlechte Leistung erbringt. Ich denke, es besteht die Tendenz zu denken, dass es immer besser ist, mehr Histogrammschritte zu haben, und dass es Bestürzung gibt, wenn SQL Server ein Histogramm für Millionen von Zeilen oder mehr generiert, aber nicht genau 200 oder 201 Histogrammschritte verwendet. Ich habe jedoch viele Statistikprobleme gesehen, selbst wenn das Histogramm 200 oder 201 Schritte hat. Wir haben keine Kontrolle darüber, wie viele Histogrammschritte SQL Server für ein Statistikobjekt generiert, sodass ich mir darüber keine Sorgen machen würde. Es gibt jedoch einige Schritte, die Sie ausführen können, wenn Abfragen mit schlechter Leistung aufgrund von Statistikproblemen auftreten. Ich werde einen äußerst kurzen Überblick geben.
In einigen Fällen kann es hilfreich sein, Statistiken vollständig zu sammeln. Bei sehr großen Tabellen kann die automatische Stichprobengröße weniger als 1% der Zeilen in der Tabelle betragen. Abhängig von der Datenstörung in der Spalte kann dies manchmal zu schlechten Plänen führen. Die Dokumentation von Microsoft für CREATE STATISTICS und UPDATE STATISTICS lautet wie folgt:
SAMPLE ist nützlich für spezielle Fälle, in denen der Abfrageplan basierend auf der Standardstichprobe nicht optimal ist. In den meisten Situationen ist es nicht erforderlich, SAMPLE anzugeben, da das Abfrageoptimierungsprogramm bereits Stichproben verwendet und standardmäßig die statistisch signifikante Stichprobengröße ermittelt, die zum Erstellen hochwertiger Abfragepläne erforderlich ist.
Für die meisten Workloads ist kein vollständiger Scan erforderlich, und die Standardabtastung ist ausreichend. Bestimmte Workloads, die für stark unterschiedliche Datenverteilungen empfindlich sind, erfordern möglicherweise eine größere Stichprobengröße oder sogar einen vollständigen Scan.
In einigen Fällen kann das Erstellen gefilterter Statistiken hilfreich sein. Möglicherweise haben Sie eine Spalte mit verzerrten Daten und vielen verschiedenen unterschiedlichen Werten. Wenn die Daten bestimmte Werte enthalten, nach denen häufig gefiltert wird, können Sie ein Statistikhistogramm nur für diese allgemeinen Werte erstellen. Das Abfrageoptimierungsprogramm kann die für einen kleineren Datenbereich definierten Statistiken anstelle der für alle Spaltenwerte definierten Statistiken verwenden. Es ist immer noch nicht garantiert, dass Sie 200 Schritte im Histogramm erhalten. Wenn Sie jedoch die gefilterten Statistiken für nur einen Wert erstellen, wird ein Histogramm diesen Wert schrittweise ausführen.
Die Verwendung einer partitionierten Ansicht ist eine Möglichkeit, mehr als 200 Schritte für eine Tabelle effektiv abzurufen. Angenommen, Sie können einen großen Tisch problemlos in einen Tisch pro Jahr aufteilen. Sie erstellen eine UNION ALL
Ansicht, in der alle Jahrestabellen zusammengefasst sind. Jede Tabelle hat ein eigenes Histogramm. Beachten Sie, dass die in SQL Server 2014 eingeführten neuen inkrementellen Statistiken nur eine effizientere Aktualisierung der Statistiken ermöglichen. Das Abfrageoptimierungsprogramm verwendet nicht die Statistiken, die pro Partition erstellt werden.
Es gibt noch viele weitere Tests, die hier durchgeführt werden könnten. Ich empfehle Ihnen daher, zu experimentieren. Ich habe diese Tests mit SQL Server 2014 Express durchgeführt, sodass Sie wirklich nichts mehr aufhält.