Hành vi kỳ lạ với kích thước mẫu để cập nhật thống kê


25

Tôi đã chơi xung quanh việc điều tra các ngưỡng lấy mẫu với các cập nhật thống kê trên SQL Server (2012) và nhận thấy một số hành vi gây tò mò. Về cơ bản, số lượng hàng được lấy mẫu dường như thay đổi trong một số trường hợp - ngay cả với cùng một bộ dữ liệu.

Tôi chạy truy vấn này:

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

Khi tôi nhìn vào đầu ra của SHOW_STATISTICS, tôi thấy rằng "Hàng được lấy mẫu" thay đổi theo từng thực thi đầy đủ (nghĩa là bảng bị bỏ, được tạo lại và được sao chép lại).

Ví dụ:

Hàng được lấy mẫu

  • 318618
  • 319240
  • 324198
  • 314154

Kỳ vọng của tôi là con số này sẽ giống nhau mỗi lần khi bảng giống hệt nhau. Nhân tiện, tôi không có hành vi này nếu tôi chỉ xóa dữ liệu và chèn lại.

Đây không phải là một câu hỏi quan trọng, nhưng tôi muốn tìm hiểu những gì đang diễn ra.


2
Có bao nhiêu tệp trong filegroup mà bạn đang chèn vào? Tôi đã thử một vài lần vào năm 2016 và cả hai lần bảng được chia thành 3584 trang với 279 hàng và 1 với 64. Hai cỡ mẫu khác nhau tôi thấy là 314712 và 315270 - cả hai đều là bội số chính xác của 279.
Martin Smith

1
@JoeObbish - Nó luôn đọc toàn bộ trang AFAIK nên tôi không ngạc nhiên về điều đó. Vì một số lý do, tôi nghĩ rằng các số trong câu hỏi không khớp với mẫu đó. Nhưng đã làm lại toán học họ làm. 318618 = 1142*279, 319240 = 1144*279 + 64, 324198=1162*279314154=1126vì thế phương sai là số lượng các trang được lấy mẫu.
Martin Smith

@MartinSmithChỉ cần một tệp - con số 279 rất thú vị, tôi luôn muốn hiểu các mẫu liên quan
Matthew McGiffen

Câu trả lời:


26

Lý lịch

Dữ liệu cho đối tượng thống kê được thu thập bằng cách sử dụng câu lệnh có dạng:

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)

Bạn có thể thu thập tuyên bố này với Sự kiện mở rộng hoặc Profiler ( SP:StmtCompleted).

Các truy vấn tạo thống kê thường truy cập vào bảng cơ sở (chứ không phải là một chỉ mục không được bao gồm) để tránh việc phân cụm các giá trị xảy ra tự nhiên trên các trang chỉ mục không được bao gồm.

Số lượng hàng được lấy mẫu tùy thuộc vào số lượng toàn bộ trang được chọn để lấy mẫu. Mỗi trang của bảng được chọn hoặc không. Tất cả các hàng trên các trang được chọn đóng góp vào số liệu thống kê.

Số ngẫu nhiên

SQL Server sử dụng trình tạo số ngẫu nhiên để quyết định xem một trang có đủ điều kiện hay không. Trình tạo được sử dụng trong trường hợp này là trình tạo số ngẫu nhiên Lehmer với các giá trị tham số như dưới đây:

X tiếp theo = X hạt giống * 7 5 mod (2 31 - 1)

Giá trị của được tính là tổng của:Xseed

  • Các nguyên thấp một phần của ( bigint) cơ sở của bảng partition_idví dụ

    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;
  • Giá trị được chỉ định trong REPEATABLEmệnh đề

    • Đối với mẫu UPDATE STATISTICS, REPEATABLEgiá trị là 1.
    • Giá trị này được hiển thị trong m_randomSeedphần tử của thông tin gỡ lỗi nội bộ của phương thức truy cập được hiển thị trong kế hoạch thực hiện khi cờ theo dõi 8666 được bật, ví dụ:<Field FieldName="m_randomSeed" FieldValue="1" />

Đối với SQL Server 2012, tính toán này xảy ra trong sqlmin!UnOrderPageScanner::StartScan:

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

trong đó bộ nhớ tại [rcx+30h]chứa 32 bit thấp của id phân vùng và bộ nhớ tại [rcx+2Ch]chứa REPEATABLEgiá trị được sử dụng.

Trình tạo số ngẫu nhiên được khởi tạo sau trong cùng một phương thức, gọi sqlmin!RandomNumGenerator::Init, trong đó hướng dẫn:

imul    r9d,r9d,41A7h

... nhân hạt giống với 41A7hex (16807 thập phân = 7 5 ) như thể hiện trong phương trình trên.

Các số ngẫu nhiên sau này (đối với các trang riêng lẻ) được tạo bằng cách sử dụng cùng một mã cơ bản được nhập vào sqlmin!UnOrderPageScanner::SetupSubScanner.

Statman

Đối với StatMantruy vấn mẫu được hiển thị ở trên, các trang tương tự sẽ được thu thập như đối với câu lệnh 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

Điều này sẽ phù hợp với đầu ra của:

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

Vỏ cạnh

Một hậu quả của việc sử dụng trình tạo số ngẫu nhiên MINSTD Lehmer là không nên sử dụng giá trị seed bằng 0 và int.max vì điều này sẽ dẫn đến thuật toán tạo ra một chuỗi các số 0 (chọn mỗi trang).

Mã phát hiện 0 và sử dụng một giá trị từ 'đồng hồ' của hệ thống làm hạt giống trong trường hợp đó. Nó không làm như vậy nếu hạt giống là int.max ( 0x7FFFFFFF= 2 31 - 1).

Chúng ta có thể thiết kế kịch bản này vì hạt giống ban đầu được tính bằng tổng 32 bit thấp của id phân vùng và REPEATABLEgiá trị. Các REPEATABLEgiá trị đó sẽ dẫn đến việc hạt giống là int.max và do đó tất cả các trang được chọn để mẫu là:

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;

Làm việc đó thành một ví dụ hoàn chỉnh:

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

Điều đó sẽ chọn mọi hàng trên mỗi trang bất kể TABLESAMPLEmệnh đề nói gì (thậm chí là không phần trăm).


11

Đây là một câu hỏi tuyệt vời! Tôi sẽ bắt đầu với những gì tôi biết chắc chắn và sau đó chuyển sang đầu cơ. Rất nhiều chi tiết về điều này trong bài viết trên blog của tôi ở đây .

Cập nhật thống kê lấy mẫu sử dụng TABLESAMPLEđằng sau hậu trường. Thật dễ dàng để tìm tài liệu về điều đó trực tuyến. Tuy nhiên, tôi tin rằng các hàng được trả về TABLESAMPLEmột phần phụ thuộc vào hobt_idđối tượng. Khi bạn thả và tạo lại đối tượng bạn nhận được một cái mới hobt_idđể các hàng được trả về bằng cách lấy mẫu ngẫu nhiên là khác nhau.

Nếu bạn xóa và xác nhận lại dữ liệu thì hobt_idvẫn giữ nguyên. Miễn là dữ liệu được trình bày theo cùng một cách trên đĩa (quét thứ tự phân bổ trả về cùng kết quả theo cùng một thứ tự) thì dữ liệu được lấy mẫu sẽ không thay đổi.

Bạn cũng có thể thay đổi số lượng hàng được lấy mẫu bằng cách xây dựng lại chỉ mục được nhóm trên bảng. Ví dụ:

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

Về lý do tại sao điều đó xảy ra, tôi tin rằng vì SQL Server quét chỉ mục được nhóm thay vì chỉ mục không được bao gồm khi thu thập các số liệu thống kê được lấy mẫu trên một chỉ mục. Tôi cũng nghĩ rằng có một giá trị ẩn (đối với những người trong chúng ta truy tìm giá trị truy vấn cập nhật thống kê ẩn) REPEATABLEđược sử dụng với TABLESAMPLE. Tôi chưa chứng minh được điều đó, nhưng nó giải thích tại sao biểu đồ của bạn và các hàng được lấy mẫu thay đổi với việc xây dựng lại chỉ mục được nhóm.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.