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_id
z
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 REPEATABLE
Klausel angegebene Wert
- Für die Stichprobe
UPDATE STATISTICS
ist der REPEATABLE
Wert 1.
- Dieser Wert wird im
m_randomSeed
Element 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 REPEATABLE
Wert.
Der Zufallszahlengenerator wird später in derselben Methode initialisiert sqlmin!RandomNumGenerator::Init
, wobei die Anweisung aufgerufen wird:
imul r9d,r9d,41A7h
... multipliziert den 41A7
Startwert 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 StatMan
oben 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 REPEATABLE
Startwert als die Summe der niedrigen 32 Bits der Partitions-ID und des Werts berechnet wird. Der REPEATABLE
Wert, 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 TABLESAMPLE
Klausel steht (sogar null Prozent).