Mengapa pemindaian lebih cepat daripada mencari predikat ini?

30

Saya dapat mereproduksi masalah kinerja kueri yang saya gambarkan sebagai tidak terduga. Saya mencari jawaban yang berfokus pada internal.

Di mesin saya, kueri berikut melakukan pemindaian indeks berkerumun dan membutuhkan sekitar 6,8 detik waktu 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);

Kueri berikut melakukan pencarian indeks berkerumun (hanya perbedaan yang menghilangkan FORCESCANpetunjuk) tetapi membutuhkan waktu CPU sekitar 18,2 detik:

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

Paket kueri sangat mirip. Untuk kedua kueri ada 120000001 baris yang dibaca dari indeks berkerumun:

rencana permintaan

Saya menggunakan SQL Server 2017 CU 10. Ini adalah kode untuk membuat dan mengisi two_col_key_testtabel:

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;

Saya berharap jawaban yang lebih dari sekadar panggilan pelaporan tumpukan. Sebagai contoh, saya dapat melihat bahwa sqlmin!TCValSSInRowExprFilter<231,0,0>::GetDataXdibutuhkan siklus CPU lebih banyak dalam permintaan lambat dibandingkan dengan yang cepat:

wawancara

Alih-alih berhenti di sana, saya ingin memahami apa itu dan mengapa ada perbedaan besar antara dua pertanyaan.

Mengapa ada perbedaan besar dalam waktu CPU untuk dua pertanyaan ini?

Joe Obbish
sumber

Jawaban:

31

Mengapa ada perbedaan besar dalam waktu CPU untuk dua pertanyaan ini?

Paket pemindaian mengevaluasi predikat (residual) terdorong non-sargable berikut untuk setiap baris:

[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'
)

memindai sisa

Rencana pencarian melakukan dua operasi pencarian:

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

... untuk mencocokkan bagian dari predikat ini:

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

Predikat residual diterapkan pada baris yang melewati kondisi pencarian di atas (semua baris dalam contoh Anda).

Namun, setiap ketidaksetaraan digantikan oleh dua tes terpisah kurang dari OR lebih besar dari :

([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')

mencari residu

Menulis ulang setiap ketidaksetaraan misalnya:

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

... kontraproduktif di sini. Perbandingan string yang sadar-collasi mahal. Menggandakan jumlah perbandingan menjelaskan sebagian besar perbedaan dalam waktu CPU yang Anda lihat.

Anda dapat melihat ini dengan lebih jelas dengan menonaktifkan pendorong predikat yang tidak dapat ditagih dengan bendera penelusuran yang tidak berdokumen 9130. Itu akan menampilkan residu sebagai Filter terpisah, dengan informasi kinerja yang dapat Anda periksa secara terpisah:

memindai

mencari

Ini juga akan menyoroti kardinalitas kecil yang salah menafsirkan pada pencarian, yang menjelaskan mengapa pengoptimal memilih pencarian daripada pemindaian di tempat pertama (itu diharapkan bagian pencarian untuk menghilangkan beberapa baris).

Sementara penulisan ulang ketimpangan dapat membuat (mungkin disaring) pencocokan indeks menjadi mungkin (untuk memanfaatkan kemampuan pencarian indeks b-tree), akan lebih baik untuk kemudian mengembalikan ekspansi ini jika kedua bagian berakhir di residual. Anda bisa menyarankan ini sebagai peningkatan pada situs umpan balik SQL Server .

Perhatikan juga bahwa model estimasi kardinalitas asli ("warisan") terjadi untuk memilih pemindaian secara default untuk kueri ini.

Paul White mengatakan GoFundMonica
sumber