Perilaku aneh dengan ukuran sampel untuk pembaruan statistik

25

Saya telah bermain-main menyelidiki ambang batas pengambilan sampel dengan pembaruan statistik pada SQL Server (2012) dan melihat beberapa perilaku yang aneh. Pada dasarnya jumlah baris sampel tampaknya bervariasi dalam beberapa keadaan - bahkan dengan set data yang sama.

Saya menjalankan kueri ini:

--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;

Ketika saya melihat output dari SHOW_STATISTICS saya menemukan bahwa "Rows Sampled" bervariasi dengan setiap eksekusi penuh (yaitu, tabel akan dijatuhkan, diciptakan kembali dan dihuni kembali).

Sebagai contoh:

Baris Sampel

  • 318618
  • 319240
  • 324198
  • 314154

Harapan saya adalah bahwa angka ini akan sama setiap kali tabelnya identik. By the way, saya tidak mendapatkan perilaku ini jika saya hanya menghapus data dan memasukkannya kembali.

Itu bukan pertanyaan kritis, tapi saya akan tertarik untuk memahami apa yang terjadi.

Matthew McGiffen
sumber
2
Berapa banyak file dalam filegroup yang Anda masukkan? Saya sudah mencoba beberapa kali pada 2016 dan kedua kali tabel itu dibagi menjadi 3584 halaman dengan 279 baris dan 1 dengan 64. Dua ukuran sampel yang berbeda yang saya lihat adalah 314712 dan 315270 - keduanya merupakan kelipatan 279.
Martin Smith
1
@ Joeybbish - Selalu membaca seluruh halaman AFAIK jadi saya tidak terkejut dengan itu. Untuk beberapa alasan saya pikir angka-angka dalam pertanyaan tidak cocok dengan pola itu. Tetapi setelah mengulang matematika yang mereka lakukan. 318618 = 1142*279, 319240 = 1144*279 + 64, 324198=1162*279Dan 314154=1126sehingga varians adalah jumlah halaman sampel.
Martin Smith
@ MartinSmith Hanya satu file - angka 279 menarik, saya selalu ingin memahami pola yang terlibat
Matthew McGiffen

Jawaban:

26

Latar Belakang

Data untuk objek statistik dikumpulkan menggunakan pernyataan formulir:

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)

Anda dapat mengumpulkan pernyataan ini dengan Extended Events atau Profiler ( SP:StmtCompleted).

Kueri pembuatan statistik sering mengakses tabel dasar (bukan indeks yang tidak dikelompokkan) untuk menghindari pengelompokan nilai yang secara alami terjadi pada halaman indeks yang tidak dikluster.

Jumlah baris sampel tergantung pada jumlah seluruh halaman yang dipilih untuk pengambilan sampel. Setiap halaman dari tabel dipilih atau tidak. Semua baris pada halaman yang dipilih berkontribusi pada statistik.

Angka acak

SQL Server menggunakan generator nomor acak untuk memutuskan apakah suatu halaman memenuhi syarat atau tidak. Generator yang digunakan dalam contoh ini adalah generator bilangan acak Lehmer dengan nilai parameter seperti yang ditunjukkan di bawah ini:

X selanjutnya = X seed * 7 5 mod (2 31 - 1)

Nilai dihitung sebagai jumlah dari:Xseed

  • Bagian integer rendah dari biginttabel dasar ( ) partition_idmisalnya

    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;
  • Nilai yang ditentukan dalam REPEATABLEklausa

    • Untuk sampel UPDATE STATISTICS, REPEATABLEnilainya 1.
    • Nilai ini terpapar dalam m_randomSeedelemen informasi debugging internal metode akses yang ditunjukkan dalam rencana eksekusi ketika jejak jejak 8666 diaktifkan, misalnya<Field FieldName="m_randomSeed" FieldValue="1" />

Untuk SQL Server 2012, perhitungan ini terjadi di sqlmin!UnOrderPageScanner::StartScan:

mov     edx,dword ptr [rcx+30h]
add     edx,dword ptr [rcx+2Ch]

di mana memori at [rcx+30h]berisi 32 bit id partisi dan memori at [rcx+2Ch]berisi REPEATABLEnilai yang digunakan.

Generator nomor acak diinisialisasi kemudian dalam metode yang sama, memanggil sqlmin!RandomNumGenerator::Init, di mana instruksi:

imul    r9d,r9d,41A7h

... mengalikan benih dengan 41A7hex (16807 desimal = 7 5 ) seperti yang ditunjukkan pada persamaan di atas.

Kemudian angka acak (untuk masing-masing halaman) dihasilkan dengan menggunakan kode dasar yang sama yang diuraikan dalam sqlmin!UnOrderPageScanner::SetupSubScanner.

StatMan

Untuk StatMankueri contoh yang ditunjukkan di atas, halaman yang sama akan dikumpulkan seperti untuk pernyataan 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

Ini akan cocok dengan output dari:

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';

Kasing tepi

Salah satu konsekuensi dari menggunakan generator angka acak MINSTD Lehmer adalah bahwa nilai seed nol dan int.max tidak boleh digunakan karena ini akan menghasilkan algoritma yang menghasilkan urutan nol (memilih setiap halaman).

Kode mendeteksi nol, dan menggunakan nilai dari 'jam' sistem sebagai seed dalam kasus itu. Itu tidak melakukan hal yang sama jika seed adalah int.max ( 0x7FFFFFFF= 2 31 - 1).

Kita dapat merekayasa skenario ini karena seed awal dihitung sebagai jumlah dari 32 bit yang rendah dari id partisi dan REPEATABLEnilainya. The REPEATABLEnilai yang akan menghasilkan benih yang int.max dan karena itu setiap halaman yang dipilih untuk sampel adalah:

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;

Mengerjakan itu menjadi contoh lengkap:

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);

Itu akan memilih setiap baris pada setiap halaman apa pun TABLESAMPLEklausa mengatakan (bahkan nol persen).

Paul White mengatakan GoFundMonica
sumber
11

Ini adalah pertanyaan yang sangat bagus! Saya akan mulai dengan apa yang saya tahu pasti dan kemudian beralih ke spekulasi. Banyak detail tentang ini di posting blog saya di sini .

Pembaruan statistik sampel digunakan di TABLESAMPLEbelakang layar. Sangat mudah untuk menemukan dokumentasi tentang hal itu secara online. Namun, saya percaya itu tidak diketahui bahwa baris yang dikembalikan TABLESAMPLEsebagian tergantung pada hobt_idobjek. Ketika Anda menjatuhkan dan membuat ulang objek, Anda mendapatkan yang baru hobt_idsehingga baris yang dikembalikan secara acak berbeda.

Jika Anda menghapus dan memasukkan kembali data hobt_idtetap sama. Selama data diletakkan dengan cara yang sama pada disk (pemindaian urutan alokasi mengembalikan hasil yang sama dalam urutan yang sama) maka data sampel tidak boleh berubah.

Anda juga dapat mengubah jumlah baris sampel dengan membangun kembali indeks berkerumun di atas meja. Sebagai contoh:

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

Seperti mengapa itu terjadi, saya percaya itu karena SQL Server memindai indeks berkerumun, bukan indeks yang tidak dikelompokkan saat mengumpulkan statistik sampel pada indeks. Saya juga berpikir bahwa ada nilai tersembunyi (bagi kita yang melacak permintaan pembaruan statistik tersembunyi) untuk REPEATABLEdigunakan bersama TABLESAMPLE. Saya belum membuktikan semua itu, tetapi itu menjelaskan mengapa histogram Anda dan baris sampel berubah dengan pembangunan kembali indeks berkerumun.

Joe Obbish
sumber