Bisakah Anda menjelaskan rencana eksekusi ini?

20

Saya sedang meneliti sesuatu yang lain ketika saya menemukan hal ini. Saya menghasilkan tabel uji dengan beberapa data di dalamnya dan menjalankan kueri yang berbeda untuk mengetahui bagaimana cara berbeda untuk menulis kueri mempengaruhi rencana eksekusi. Berikut ini skrip yang saya gunakan untuk menghasilkan data uji acak:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U'))
DROP TABLE t
GO

CREATE TABLE t 
(
 c1 int IDENTITY(1,1) NOT NULL 
,c2 int NULL
) 
GO

insert into t
select top 1000000 a from
(select t1.number*2048 + t2.number a, newid() b
from [master]..spt_values t1 
cross join  [master]..spt_values t2
where t1.[type] = 'P' and t2.[type] = 'P') a
order by b
GO

update t set c2 = null
where c2 < 2048 * 2048 / 10
GO


CREATE CLUSTERED INDEX pk ON [t] (c1)
GO

CREATE NONCLUSTERED INDEX i ON t (c2)
GO

Sekarang, mengingat data ini, saya meminta pertanyaan berikut:

select * 
from t 
where 
      c2 < 1048576 
   or c2 is null
;

Yang sangat mengejutkan saya, rencana eksekusi yang dihasilkan untuk permintaan ini, adalah ini . (Maaf untuk tautan eksternal, terlalu besar untuk muat di sini).

Adakah yang bisa menjelaskan kepada saya apa yang terjadi dengan semua " Pemindaian Konstan " dan " Kalkulator Komputasi " ini? Apa yang terjadi?

Rencana

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1012]))
       |--Merge Interval
       |    |--Sort(TOP 2, ORDER BY:([Expr1013] DESC, [Expr1014] ASC, [Expr1010] ASC, [Expr1015] DESC))
       |         |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010], [Expr1014]=(4)&[Expr1012], [Expr1015]=(16)&[Expr1012]))
       |              |--Concatenation
       |                   |--Compute Scalar(DEFINE:([Expr1005]=NULL, [Expr1006]=NULL, [Expr1004]=(60)))
       |                   |    |--Constant Scan
       |                   |--Compute Scalar(DEFINE:([Expr1008]=NULL, [Expr1009]=(1048576), [Expr1007]=(10)))
       |                        |--Constant Scan
       |--Index Seek(OBJECT:([t].[i]), SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)
Andrew Savinykh
sumber

Jawaban:

29

Pemindaian konstan masing-masing menghasilkan satu baris dalam memori tanpa kolom. Skalar komputasi atas menghasilkan satu baris dengan 3 kolom

Expr1005    Expr1006    Expr1004
----------- ----------- -----------
NULL        NULL        60

Skalar komputasi bawah menghasilkan satu baris dengan 3 kolom

Expr1008    Expr1009    Expr1007
----------- ----------- -----------
NULL        1048576        10

Operator gabungan Serikat 2 baris ini bersama-sama dan menampilkan 3 kolom tetapi mereka sekarang berganti nama

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

The Expr1012kolom adalah satu set bendera digunakan secara internal untuk menentukan tertentu mencari properti untuk penyimpanan mesin .

Hitung skalar berikutnya di sepanjang menghasilkan 2 baris

Expr1010    Expr1011    Expr1012    Expr1013    Expr1014    Expr1015
----------- ----------- ----------- ----------- ----------- -----------
NULL        NULL        60          True        4           16            
NULL        1048576     10          False       0           0      

Tiga kolom terakhir didefinisikan sebagai berikut dan hanya digunakan untuk tujuan penyortiran sebelum disajikan kepada Operator Interval Gabung

[Expr1013] = Scalar Operator(((4)&[Expr1012]) = (4) AND NULL = [Expr1010]), 
[Expr1014] = Scalar Operator((4)&[Expr1012]), 
[Expr1015] = Scalar Operator((16)&[Expr1012])

Expr1014dan Expr1015hanya menguji apakah bit-bit tertentu dalam bendera. Expr1013muncul kembali kolom boolean true jika kedua bit untuk 4aktif dan Expr1010adalah NULL.

Dari mencoba operator perbandingan lain dalam kueri, saya mendapatkan hasil ini

+----------+----------+----------+-------------+----+----+---+---+---+---+
| Operator | Expr1010 | Expr1011 | Flags (Dec) |       Flags (Bin)       |
|          |          |          |             | 32 | 16 | 8 | 4 | 2 | 1 |
+----------+----------+----------+-------------+----+----+---+---+---+---+
| >        | 1048576  | NULL     |           6 |  0 |  0 | 0 | 1 | 1 | 0 |
| >=       | 1048576  | NULL     |          22 |  0 |  1 | 0 | 1 | 1 | 0 |
| <=       | NULL     | 1048576  |          42 |  1 |  0 | 1 | 0 | 1 | 0 |
| <        | NULL     | 1048576  |          10 |  0 |  0 | 1 | 0 | 1 | 0 |
| =        | 1048576  | 1048576  |          62 |  1 |  1 | 1 | 1 | 1 | 0 |
| IS NULL  | NULL     | NULL     |          60 |  1 |  1 | 1 | 1 | 0 | 0 |
+----------+----------+----------+-------------+----+----+---+---+---+---+

Dari yang saya simpulkan bahwa Bit 4 berarti "Telah mulai dari kisaran" (sebagai lawan tidak terikat) dan Bit 16 berarti mulai dari kisaran inklusif.

Kumpulan hasil 6 kolom ini dikeluarkan dari SORToperator yang diurut berdasarkan Expr1013 DESC, Expr1014 ASC, Expr1010 ASC, Expr1015 DESC. Dengan asumsi Truediwakili oleh 1dan Falseoleh 0resultset yang diwakili sebelumnya sudah dalam urutan itu.

Berdasarkan asumsi saya sebelumnya, efek bersih dari jenis ini adalah untuk menyajikan rentang ke interval penggabungan dalam urutan berikut

 ORDER BY 
          HasStartOfRangeAndItIsNullFirst,
          HasUnboundedStartOfRangeFirst,
          StartOfRange,
          StartOfRangeIsInclusiveFirst

Operator interval gabungan menghasilkan 2 baris

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Untuk setiap baris yang dipancarkan, pencarian rentang dilakukan

Seek Keys[1]: Start:[dbo].[t].c2 > Scalar Operator([Expr1010]), 
               End: [dbo].[t].c2 < Scalar Operator([Expr1011])

Jadi akan tampak seolah-olah dua pencarian dilakukan. Satu rupanya > NULL AND < NULLdan satu > NULL AND < 1048576. Namun bendera yang dilewati muncul untuk memodifikasi ini IS NULLdan < 1048576masing - masing. Semoga @sqlkiwi dapat mengklarifikasi ini dan memperbaiki segala ketidakakuratan!

Jika Anda mengubah kueri menjadi sedikit

select *
from t 
where 
      c2 > 1048576 
   or c2 = 0
;

Kemudian rencana tersebut terlihat jauh lebih sederhana dengan pencarian indeks dengan beberapa predikat pencarian.

Rencana itu menunjukkan Seek Keys

Start: c2 >= 0, End: c2 <= 0, 
Start: c2 > 1048576

Penjelasan mengapa rencana sederhana ini tidak dapat digunakan untuk kasus dalam OP diberikan oleh SQLKiwi dalam komentar untuk posting blog sebelumnya yang ditautkan .

Pencarian indeks dengan beberapa predikat tidak dapat mencampur berbagai jenis predikat perbandingan (mis. IsDan Eqdalam kasus di OP). Ini hanyalah batasan saat ini dari produk (dan mungkin merupakan alasan mengapa uji kesetaraan dalam kueri terakhir c2 = 0diimplementasikan menggunakan >=dan <=bukan hanya kesetaraan langsung mencari Anda dapatkan untuk kueri c2 = 0 OR c2 = 1048576.

Martin Smith
sumber
Saya tidak dapat menemukan apa pun di artikel Paul yang menjelaskan perbedaan pada bendera untuk [Expr1012]. Bisakah Anda menyimpulkan apa yang ditandakan oleh 60/10 di sini?
Mark Storey-Smith
@ MarkStorey-Smith - katanya 62untuk perbandingan kesetaraan. Saya kira 60pasti berarti bahwa alih-alih > AND < seperti yang ditunjukkan dalam rencana Anda sebenarnya dapatkan >= AND <=kecuali itu adalah IS NULLbendera eksplisit mungkin (?) Atau mungkin bit 2menunjukkan sesuatu yang lain yang tidak terkait dan 60masih sama seperti ketika saya lakukan set ansi_nulls offdan mengubahnya untuk c2 = nullitu masih tetap di60
Martin Smith
2
@MartinSmith 60 memang untuk perbandingan dengan NULL. Ekspresi batas rentang menggunakan NULL untuk mewakili 'tidak terikat' di kedua ujungnya. Pencarian selalu eksklusif yaitu mencari Mulai:> Expr & End: <Expr daripada inklusif menggunakan> = dan <=. Terima kasih atas komentar blognya, saya akan memposting jawaban atau komentar yang lebih panjang sebagai balasan di pagi hari (terlambat untuk melakukannya sekarang).
Paul White mengatakan GoFundMonica
@SQLKiwi - Terima kasih. Itu masuk akal. Mudah-mudahan saya akan menemukan beberapa bagian yang hilang sebelum itu.
Martin Smith
Terima kasih banyak, saya masih menyerap ini, tetapi sepertinya menjelaskan dengan baik, pertanyaan utama yang tersisa adalah pertanyaan yang Anda tanyakan pada @SQLKiwi di blognya. Saya akan merenungkan beberapa hari lagi jawaban Anda untuk memastikan saya tidak memiliki pertanyaan lanjutan dan saya akan menerima jawaban Anda. Sekali lagi terima kasih, itu sangat membantu.
Andrew Savinykh
13

Pemindaian konstan adalah cara untuk SQL Server untuk membuat ember di mana itu akan menempatkan sesuatu nanti dalam rencana eksekusi. Saya telah memposting penjelasan yang lebih menyeluruh di sini . Untuk memahami untuk apa pemindaian konstan, Anda harus melihat lebih jauh ke dalam rencana. Dalam kasus ini, operator Compute Scalar yang digunakan untuk mengisi ruang yang dibuat oleh pemindaian konstan.

Operator Compute Scalar sedang dimuat dengan NULL dan nilai 1045876, jadi mereka jelas akan digunakan dengan Loop Join dalam upaya untuk memfilter data.

Bagian yang sangat keren adalah bahwa rencana ini adalah Sepele. Ini berarti bahwa ia melewati proses optimasi minimal. Semua operasi mengarah ke Interval Penggabungan. Ini digunakan untuk membuat satu set minimal operator perbandingan untuk pencarian indeks ( detailnya di sini ).

Idenya adalah untuk menyingkirkan nilai yang tumpang tindih sehingga kemudian dapat menarik data dengan melewati minimal. Meskipun masih menggunakan operasi loop, Anda akan perhatikan bahwa loop dieksekusi tepat sekali, yang berarti, itu secara efektif memindai.

ADDENDUM: Kalimat terakhir tidak aktif. Ada dua upaya. Saya salah membaca rencana. Sisa konsepnya sama dan tujuannya, operan minimal, sama.

Berikan Fritchey
sumber