Tại sao quét nhanh hơn tìm kiếm vị từ này?


30

Tôi đã có thể tái tạo một vấn đề hiệu năng truy vấn mà tôi sẽ mô tả là bất ngờ. Tôi đang tìm kiếm một câu trả lời tập trung vào nội bộ.

Trên máy của tôi, truy vấn sau đây thực hiện quét chỉ mục theo cụm và mất khoảng 6,8 giây thời gian CPU:

SELECT ID1, ID2
FROM two_col_key_test WITH (FORCESCAN)
WHERE ID1 NOT IN
(
N'1', N'2',N'3', N'4', N'5',
N'6', N'7', N'8', N'9', N'10',
N'11', N'12',N'13', N'14', N'15',
N'16', N'17', N'18', N'19', N'20'
)
AND (ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))
ORDER BY ID1, ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY
OPTION (MAXDOP 1);

Truy vấn sau đây thực hiện tìm kiếm chỉ mục theo cụm (chỉ khác là xóa FORCESCANgợi ý) nhưng mất khoảng 18,2 giây thời gian CPU:

SELECT ID1, ID2
FROM two_col_key_test
WHERE ID1 NOT IN
(
N'1', N'2',N'3', N'4', N'5',
N'6', N'7', N'8', N'9', N'10',
N'11', N'12',N'13', N'14', N'15',
N'16', N'17', N'18', N'19', N'20'
)
AND (ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))
ORDER BY ID1, ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY
OPTION (MAXDOP 1);

Các kế hoạch truy vấn là khá giống nhau. Đối với cả hai truy vấn, có 120000001 hàng được đọc từ chỉ mục được nhóm:

kế hoạch truy vấn

Tôi đang trên SQL Server 2017 CU 10. Đây là mã để tạo và điền vào two_col_key_testbảng:

drop table if exists dbo.two_col_key_test;

CREATE TABLE dbo.two_col_key_test (
    ID1 NVARCHAR(50) NOT NULL,
    ID2 NVARCHAR(50) NOT NULL,
    FILLER NVARCHAR(50),
    PRIMARY KEY (ID1, ID2)
);

DROP TABLE IF EXISTS #t;

SELECT TOP (4000) 0 ID INTO #t
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);


INSERT INTO dbo.two_col_key_test WITH (TABLOCK)
SELECT N'FILLER TEXT' + CASE WHEN ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) > 8000000 THEN N' 2' ELSE N'' END
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, NULL
FROM #t t1
CROSS JOIN #t t2;

Tôi hy vọng cho một câu trả lời không chỉ là báo cáo ngăn xếp cuộc gọi. Ví dụ, tôi có thể thấy rằng sqlmin!TCValSSInRowExprFilter<231,0,0>::GetDataXcần nhiều chu kỳ CPU hơn trong truy vấn chậm so với truy vấn nhanh:

perview

Thay vì dừng lại ở đó, tôi muốn hiểu đó là gì và tại sao có sự khác biệt lớn như vậy giữa hai truy vấn.

Tại sao có sự khác biệt lớn về thời gian CPU cho hai truy vấn này?

Câu trả lời:


31

Tại sao có sự khác biệt lớn về thời gian CPU cho hai truy vấn này?

Kế hoạch quét đánh giá các biến vị ngữ được đẩy không thể vượt qua (dư) sau đây cho mỗi hàng:

[two_col_key_test].[ID1]<>N'1' 
AND [two_col_key_test].[ID1]<>N'10' 
AND [two_col_key_test].[ID1]<>N'11' 
AND [two_col_key_test].[ID1]<>N'12' 
AND [two_col_key_test].[ID1]<>N'13' 
AND [two_col_key_test].[ID1]<>N'14' 
AND [two_col_key_test].[ID1]<>N'15' 
AND [two_col_key_test].[ID1]<>N'16' 
AND [two_col_key_test].[ID1]<>N'17' 
AND [two_col_key_test].[ID1]<>N'18' 
AND [two_col_key_test].[ID1]<>N'19' 
AND [two_col_key_test].[ID1]<>N'2' 
AND [two_col_key_test].[ID1]<>N'20' 
AND [two_col_key_test].[ID1]<>N'3' 
AND [two_col_key_test].[ID1]<>N'4' 
AND [two_col_key_test].[ID1]<>N'5' 
AND [two_col_key_test].[ID1]<>N'6' 
AND [two_col_key_test].[ID1]<>N'7' 
AND [two_col_key_test].[ID1]<>N'8' 
AND [two_col_key_test].[ID1]<>N'9' 
AND 
(
    [two_col_key_test].[ID1]=N'FILLER TEXT' 
    AND [two_col_key_test].[ID2]>=N'' 
    OR [two_col_key_test].[ID1]>N'FILLER TEXT'
)

quét dư

Kế hoạch tìm kiếm thực hiện hai hoạt động tìm kiếm:

Seek Keys[1]: 
    Prefix: 
    [two_col_key_test].ID1 = Scalar Operator(N'FILLER TEXT'), 
        Start: [two_col_key_test].ID2 >= Scalar Operator(N'')
Seek Keys[1]: 
    Start: [two_col_key_test].ID1 > Scalar Operator(N'FILLER TEXT')

... để phù hợp với phần này của vị ngữ:

(ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))

Một vị từ còn lại được áp dụng cho các hàng vượt qua các điều kiện tìm kiếm ở trên (tất cả các hàng trong ví dụ của bạn).

Tuy nhiên, mỗi bất đẳng thức được thay thế bằng hai thử nghiệm riêng biệt với giá trị nhỏ OR hơn :

([two_col_key_test].[ID1]<N'1' OR [two_col_key_test].[ID1]>N'1') 
AND ([two_col_key_test].[ID1]<N'10' OR [two_col_key_test].[ID1]>N'10') 
AND ([two_col_key_test].[ID1]<N'11' OR [two_col_key_test].[ID1]>N'11') 
AND ([two_col_key_test].[ID1]<N'12' OR [two_col_key_test].[ID1]>N'12') 
AND ([two_col_key_test].[ID1]<N'13' OR [two_col_key_test].[ID1]>N'13') 
AND ([two_col_key_test].[ID1]<N'14' OR [two_col_key_test].[ID1]>N'14') 
AND ([two_col_key_test].[ID1]<N'15' OR [two_col_key_test].[ID1]>N'15') 
AND ([two_col_key_test].[ID1]<N'16' OR [two_col_key_test].[ID1]>N'16') 
AND ([two_col_key_test].[ID1]<N'17' OR [two_col_key_test].[ID1]>N'17') 
AND ([two_col_key_test].[ID1]<N'18' OR [two_col_key_test].[ID1]>N'18') 
AND ([two_col_key_test].[ID1]<N'19' OR [two_col_key_test].[ID1]>N'19') 
AND ([two_col_key_test].[ID1]<N'2' OR [two_col_key_test].[ID1]>N'2') 
AND ([two_col_key_test].[ID1]<N'20' OR [two_col_key_test].[ID1]>N'20') 
AND ([two_col_key_test].[ID1]<N'3' OR [two_col_key_test].[ID1]>N'3') 
AND ([two_col_key_test].[ID1]<N'4' OR [two_col_key_test].[ID1]>N'4') 
AND ([two_col_key_test].[ID1]<N'5' OR [two_col_key_test].[ID1]>N'5') 
AND ([two_col_key_test].[ID1]<N'6' OR [two_col_key_test].[ID1]>N'6') 
AND ([two_col_key_test].[ID1]<N'7' OR [two_col_key_test].[ID1]>N'7') 
AND ([two_col_key_test].[ID1]<N'8' OR [two_col_key_test].[ID1]>N'8') 
AND ([two_col_key_test].[ID1]<N'9' OR [two_col_key_test].[ID1]>N'9')

tìm kiếm dư

Viết lại mỗi bất đẳng thức, ví dụ:

[ID1] <> N'1'  ->  [ID1]<N'1' OR [ID1]>N'1'

... là phản tác dụng ở đây. So sánh chuỗi đối chiếu là đắt tiền. Nhân đôi số lượng so sánh giải thích hầu hết sự khác biệt về thời gian CPU mà bạn thấy.

Bạn có thể thấy rõ hơn điều này bằng cách vô hiệu hóa việc đẩy các biến vị ngữ không thể quét được bằng cờ theo dõi không có giấy tờ 9130. Điều đó sẽ hiển thị phần dư dưới dạng Bộ lọc riêng biệt, với thông tin hiệu suất bạn có thể kiểm tra riêng:

quét

tìm kiếm

Điều này cũng sẽ làm nổi bật tính sai lệch nhỏ của cardinality đối với tìm kiếm, điều này giải thích tại sao trình tối ưu hóa chọn tìm kiếm trong lần quét đầu tiên (dự kiến ​​phần tìm kiếm sẽ loại bỏ một số hàng).

Mặc dù việc viết lại bất đẳng thức có thể làm cho chỉ số phù hợp (có thể được lọc) có thể khớp (để sử dụng tốt nhất khả năng tìm kiếm của các chỉ mục b-cây), tốt hơn là nên hoàn nguyên việc mở rộng này nếu cả hai nửa kết thúc ở phần dư. Bạn có thể đề xuất điều này như một sự cải tiến trên trang web phản hồi của SQL Server .

Cũng lưu ý rằng mô hình ước tính cardinality gốc ("di sản") xảy ra để chọn quét theo mặc định cho truy vấn này.

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.