tło
Dane dla obiektu statystyki są gromadzone za pomocą instrukcji formularza:
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)
Możesz zebrać to oświadczenie za pomocą Rozszerzonych zdarzeń lub Profilera (SP:StmtCompleted
).
Kwerendy generowania statystyk często uzyskują dostęp do tabeli podstawowej (zamiast indeksu nieklastrowanego), aby uniknąć grupowania wartości, które naturalnie występują na nieklastrowanych stronach indeksu.
Liczba próbkowanych wierszy zależy od liczby całych stron wybranych do próbkowania. Każda strona tabeli jest albo wybrana, albo nie. Wszystkie wiersze na wybranych stronach przyczyniają się do statystyk.
Losowe liczby
SQL Server używa generatora liczb losowych, aby zdecydować, czy strona się kwalifikuje, czy nie. Generatorem zastosowanym w tym przypadku jest generator liczb losowych Lehmera z wartościami parametrów przedstawionymi poniżej:
X dalej = X ziarno * 7 5 mod (2 31 )
Wartość jest obliczana jako suma:Xseed
Niska liczba całkowita części bigint
tabeli podstawowej ( ) partition_id
np
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;
Wartość określona w REPEATABLE
klauzuli
- Dla próbki
UPDATE STATISTICS
,REPEATABLE
wartość 1.
- Ta wartość jest ujawniana w
m_randomSeed
elemencie informacji wewnętrznych debugowania metody dostępu wyświetlanych w planach wykonania, gdy flaga śledzenia 8666 jest włączona, na przykład<Field FieldName="m_randomSeed" FieldValue="1" />
W przypadku programu SQL Server 2012 obliczenia te występują w sqlmin!UnOrderPageScanner::StartScan
:
mov edx,dword ptr [rcx+30h]
add edx,dword ptr [rcx+2Ch]
gdzie pamięć at [rcx+30h]
zawiera 32 niskie bity identyfikatora partycji, a pamięć at [rcx+2Ch]
zawieraREPEATABLE
używaną wartość.
Generator liczb losowych jest inicjowany później tą samą metodą, wywołując sqlmin!RandomNumGenerator::Init
, gdzie instrukcja:
imul r9d,r9d,41A7h
... rozmnaża się ziarno o 41A7
sześciokątnych (16807 dziesiętny = 7 5 ), jak pokazano w równaniu powyżej.
Później losowe liczby (dla poszczególnych stron) są generowane przy użyciu tego samego podstawowego kodu wstawionego do sqlmin!UnOrderPageScanner::SetupSubScanner
.
StatMan
Dla przykładowego StatMan
zapytania pokazanego powyżej zostaną zebrane te same strony, co w przypadku instrukcji T-SQL:
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
To dopasuje wynik:
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';
Obudowa krawędzi
Jedną konsekwencją użycia generatora liczb losowych MINSTD Lehmera jest to, że nie należy stosować wartości początkowych zero i int.max, ponieważ spowoduje to, że algorytm wygeneruje sekwencję zer (wybieranie każdej strony).
Kod wykrywa zero i używa wartości z systemowego „zegara” jako źródła w tym przypadku. Nie robi to samo, jeśli ziarno ma wartość int.max ( 0x7FFFFFFF
= 2 31 - 1).
Możemy zaprojektować ten scenariusz, ponieważ początkowe ziarno jest obliczane jako suma niskich 32 bitów identyfikatora partycji i REPEATABLE
wartości. REPEATABLE
Wartość, która spowoduje, że materiał siewny jest int.max i dlatego każda strona jest wybrana próbka:
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;
Przekształcenie tego w pełny przykład:
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);
Spowoduje to zaznaczenie każdego wiersza na każdej stronie, niezależnie od TABLESAMPLE
klauzuli (nawet zero procent).