Hintergrund
Daten für das Statistikobjekt werden mit einer Anweisung der Form gesammelt:
SELECT
StatMan([SC0], [SC1], [SB0000])
FROM
(
SELECT TOP 100 PERCENT
[SC0], [SC1], STEP_DIRECTION([SC0]) OVER (ORDER BY NULL) AS [SB0000]
FROM
(
SELECT
[TextValue] AS [SC0],
[Id] AS [SC1]
FROM [dbo].[Test]
TABLESAMPLE SYSTEM (2.223684e+001 PERCENT)
WITH (READUNCOMMITTED)
) AS _MS_UPDSTATS_TBL_HELPER
ORDER BY
[SC0],
[SC1],
[SB0000]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1)
Sie können diese Anweisung mit Extended Events oder Profiler ( SP:StmtCompleted) sammeln .
Abfragen zur Generierung von Statistiken greifen häufig auf die Basistabelle zu (anstatt auf einen nicht gruppierten Index), um das Clustering von Werten zu vermeiden, das normalerweise auf nicht gruppierten Indexseiten auftritt.
Die Anzahl der abgetasteten Zeilen hängt von der Anzahl der zum Abtasten ausgewählten ganzen Seiten ab. Jede Seite der Tabelle ist entweder ausgewählt oder nicht. Alle Zeilen auf ausgewählten Seiten tragen zur Statistik bei.
Zufällige Zahlen
SQL Server verwendet einen Zufallszahlengenerator, um zu entscheiden, ob eine Seite qualifiziert ist oder nicht. Der in diesem Fall verwendete Generator ist der Lehmer-Zufallszahlengenerator mit folgenden Parameterwerten:
X next = X seed * 7 5 mod (2 31 - 1)
Der Wert von wird berechnet als die Summe von:Xseed
Der niedrige ganzzahlige Teil der ( bigint) Basistabelle ist partition_idz
SELECT
P.[partition_id] & 0xFFFFFFFF
FROM sys.partitions AS P
WHERE
P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
AND P.index_id = 1;
Der in der REPEATABLEKlausel angegebene Wert
- Für die Stichprobe
UPDATE STATISTICSist der REPEATABLEWert 1.
- Dieser Wert wird im
m_randomSeedElement der internen Debugging-Informationen der Zugriffsmethode angezeigt, die in Ausführungsplänen angezeigt werden, wenn beispielsweise das Ablaufverfolgungsflag 8666 aktiviert ist<Field FieldName="m_randomSeed" FieldValue="1" />
Für SQL Server 2012 erfolgt diese Berechnung in sqlmin!UnOrderPageScanner::StartScan:
mov edx,dword ptr [rcx+30h]
add edx,dword ptr [rcx+2Ch]
Dabei [rcx+30h]enthält memory at die niedrigen 32 Bits der Partitions-ID und memory at [rcx+2Ch]den verwendeten REPEATABLEWert.
Der Zufallszahlengenerator wird später in derselben Methode initialisiert sqlmin!RandomNumGenerator::Init, wobei die Anweisung aufgerufen wird:
imul r9d,r9d,41A7h
... multipliziert den 41A7Startwert mit dem Hexadezimalwert (16807 Dezimal = 7 5 ), wie in der obigen Gleichung gezeigt.
Spätere Zufallszahlen (für einzelne Seiten) werden mit dem gleichen Basiscode wie in generiert sqlmin!UnOrderPageScanner::SetupSubScanner.
StatMan
Für die StatManoben gezeigte Beispielabfrage werden dieselben Seiten wie für die T-SQL-Anweisung gesammelt:
SELECT
COUNT_BIG(*)
FROM dbo.Test AS T
TABLESAMPLE SYSTEM (2.223684e+001 PERCENT) -- Same sample %
REPEATABLE (1) -- Always 1 for statman
WITH (INDEX(0)); -- Scan base object
Dies entspricht der Ausgabe von:
SELECT
DDSP.rows_sampled
FROM sys.stats AS S
CROSS APPLY sys.dm_db_stats_properties(S.[object_id], S.stats_id) AS DDSP
WHERE
S.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
AND S.[name] = N'IX_Test_TextValue';
Edge-Fall
Eine Konsequenz der Verwendung des Zufallszahlengenerators von MINSTD Lehmer ist, dass die Startwerte Null und int.max nicht verwendet werden sollten, da dies dazu führt, dass der Algorithmus eine Folge von Nullen erzeugt (Auswahl jeder Seite).
Der Code erkennt Null und verwendet in diesem Fall einen Wert aus der Systemuhr als Startwert. Dies ist nicht der Fall, wenn der Startwert int.max ( 0x7FFFFFFF= 2 31 - 1) ist.
Wir können dieses Szenario konstruieren, da der anfängliche REPEATABLEStartwert als die Summe der niedrigen 32 Bits der Partitions-ID und des Werts berechnet wird. Der REPEATABLEWert, der dazu führt, dass der Startwert int.max ist und daher jede Seite für die Stichprobe ausgewählt wird, ist:
SELECT
0x7FFFFFFF - (P.[partition_id] & 0xFFFFFFFF)
FROM sys.partitions AS P
WHERE
P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
AND P.index_id = 1;
Das in ein vollständiges Beispiel umwandeln:
DECLARE @SQL nvarchar(4000) =
N'
SELECT
COUNT_BIG(*)
FROM dbo.Test AS T
TABLESAMPLE (0 PERCENT)
REPEATABLE (' +
(
SELECT TOP (1)
CONVERT(nvarchar(11), 0x7FFFFFFF - P.[partition_id] & 0xFFFFFFFF)
FROM sys.partitions AS P
WHERE
P.[object_id] = OBJECT_ID(N'dbo.Test', N'U')
AND P.index_id = 1
) + ')
WITH (INDEX(0));';
PRINT @SQL;
--EXECUTE (@SQL);
Dadurch wird jede Zeile auf jeder Seite ausgewählt, unabhängig davon, was in der TABLESAMPLEKlausel steht (sogar null Prozent).