SQL Server(2012)で統計情報の更新を使用してサンプリングしきい値の調査に取り組み、奇妙な動作に気づきました。基本的に、同じデータセットであっても、サンプリングされる行数は状況によって異なるようです。
私はこのクエリを実行します:
--Drop table if exists
IF (OBJECT_ID('dbo.Test')) IS NOT NULL DROP TABLE dbo.Test;
--Create Table for Testing
CREATE TABLE dbo.Test(Id INT IDENTITY(1,1) CONSTRAINT PK_Test PRIMARY KEY CLUSTERED, TextValue VARCHAR(20) NULL);
--Insert enough data so we have more than 8Mb (the threshold at which sampling kicks in)
INSERT INTO dbo.Test(TextValue)
SELECT TOP 1000000 'blahblahblah'
FROM sys.objects a, sys.objects b, sys.objects c, sys.objects d;
--Create Index on TextValue
CREATE INDEX IX_Test_TextValue ON dbo.Test(TextValue);
--Update Statistics without specifying how many rows to sample
UPDATE STATISTICS dbo.Test IX_Test_TextValue;
--View the Statistics
DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER;
SHOW_STATISTICSの出力を見ると、「サンプリングされた行」が実行のたびに変化することがわかります(つまり、テーブルが削除され、再作成され、再入力されます)。
例えば:
サンプリングされた行
テーブルが同一であるため、この数値は毎回同じになると予想していました。ちなみに、データを削除して再挿入しただけでは、この動作は発生しません。
それは重要な質問ではありませんが、私は何が起こっているのかを理解することに興味があります。
統計オブジェクトのデータは、次の形式のステートメントを使用して収集されます。
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)
このステートメントは、拡張イベントまたはプロファイラー(SP:StmtCompleted
)で収集できます。
統計生成クエリは、非クラスター化インデックスページで自然に発生する値のクラスター化を回避するために、(非クラスター化インデックスではなく)ベーステーブルにアクセスすることがよくあります。
サンプリングされる行数は、サンプリングするために選択されたページ全体の数によって異なります。テーブルの各ページが選択されているか、選択されていません。選択したページのすべての行が統計に寄与します。
SQL Serverは、乱数ジェネレータを使用して、ページが適格かどうかを判断します。このインスタンスで使用されるジェネレーターは Lehmer乱数ジェネレーター であり、パラメーター値は以下のとおりです。
バツ次 = Xシード * 75 mod(231 -1)
Xseed
の値は、次の合計として計算されます。
(bigint
)ベーステーブルのpartition_id
の低整数部分。
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;
REPEATABLE
句で指定された値
UPDATE STATISTICS
の場合、REPEATABLE
の値は1です。m_randomSeed
要素で公開されます(例:<Field FieldName="m_randomSeed" FieldValue="1" />
)。SQL Server 2012の場合、この計算はsqlmin!UnOrderPageScanner::StartScan
で行われます。
mov edx,dword ptr [rcx+30h]
add edx,dword ptr [rcx+2Ch]
ここで、[rcx+30h]
のメモリにはパーティションIDの下位32ビットが含まれ、[rcx+2Ch]
のメモリには使用中のREPEATABLE
値が含まれます。
乱数ジェネレータは後で同じメソッドで初期化され、sqlmin!RandomNumGenerator::Init
を呼び出します。
imul r9d,r9d,41A7h
...シードに41A7
hex(16807 16進数= 7)を乗算します5)上記の式に示すように。
後で(個々のページの)乱数は、sqlmin!UnOrderPageScanner::SetupSubScanner
にインラインされた同じ基本コードを使用して生成されます。
上記のStatMan
クエリの例では、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
これは次の出力と一致します。
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';
MINSTD Lehmer乱数ジェネレータを使用すると、シード値0とint.maxを使用しないでください。これにより、アルゴリズムによって一連のゼロが生成されます(すべてのページを選択)。
コードはゼロを検出し、その場合、シードとしてシステムの「クロック」からの値を使用します。シードがint.max(0x7FFFFFFF
= 231 -1)。
初期シードはパーティションIDの下位32ビットとREPEATABLE
値の合計として計算されるため、このシナリオを設計できます。シードがint.maxになるREPEATABLE
の値は、サンプルとして選択されるすべてのページです。
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;
それを完全な例に組み込む:
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);
これにより、TABLESAMPLE
句の内容に関係なく、すべてのページのすべての行が選択されます(ゼロパーセントでも)。
これはすばらしい質問です。私は確かに私が知っていることから始めて、そして推測に移ります。これに関する多くの詳細は私のブログ投稿 here にあります。
サンプル統計更新では、裏でTABLESAMPLE
を使用します。そのドキュメントをオンラインで簡単に見つけることができます。ただし、TABLESAMPLE
によって返される行がオブジェクトのhobt_id
に部分的に依存していることはよく知られていないと思います。オブジェクトをドロップして再作成すると、新しいhobt_id
が取得されるため、ランダムサンプリングによって返される行は異なります。
データを削除して再挿入しても、hobt_id
は同じままです。データがディスク上で同じように配置されている限り(割り当て順序スキャンが同じ結果を同じ順序で返す)、サンプリングされたデータは変更されません。
テーブルでクラスター化インデックスを再構築することにより、サンプリングされる行数を変更することもできます。例えば:
UPDATE STATISTICS dbo.Test IX_Test_TextValue;
DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER; -- 273862 rows
ALTER INDEX PK_Test on Test REBUILD;
UPDATE STATISTICS dbo.Test IX_Test_TextValue;
DBCC SHOW_STATISTICS('dbo.Test', IX_Test_TextValue) WITH STAT_HEADER; -- 273320 rows
これが発生する理由については、SQL Serverがインデックスのサンプル統計を収集するときに、非クラスター化インデックスではなくクラスター化インデックスをスキャンするためだと思います。また、REPEATABLE
で使用されるTABLESAMPLE
には、(非表示の統計更新クエリをトレースする)非表示の値があると思います。それはまだ証明されていませんが、ヒストグラムとサンプリングされた行がクラスター化インデックスの再構築によって変化する理由を説明しています。
ページごとにランダムな確率を割り当てるという点でTABLESAMPLEがどのように機能するかを忘れました。 - Martin Smith
Itzik Ben-Ganによる Microsoft SQL Server 2008内:T-SQLクエリ でこれを見て、コメントとして追加できないので、ここに投稿します。他の人にとっても興味深いと思います。
Rojiによる TABLESAMPLEを使用したサンプリング も参照してください。 P.トーマス。