Dari mana asal Scan Konstanta dan Gabung Kiri Luar ini dalam rencana kueri SELECT sepele?

21

Saya punya tabel ini:

CREATE TABLE [dbo].[Accounts] (
    [AccountId] UNIQUEIDENTIFIER UNIQUE NOT NULL DEFAULT NEWID(),
    -- WHATEVER other columns
);
GO
CREATE UNIQUE CLUSTERED INDEX [AccountsIndex]
    ON [dbo].[Accounts]([AccountId] ASC);
GO

Kueri ini:

DECLARE @result UNIQUEIDENTIFIER
SELECT @result = AccountId FROM Accounts WHERE AccountId='guid-here'

dijalankan dengan rencana kueri yang terdiri dari Pencarian Indeks tunggal - seperti yang diharapkan:

SELECT <---- Clustered Index Seek

Kueri ini melakukan hal yang sama:

DECLARE @result UNIQUEIDENTIFIER
SET @result = (SELECT AccountId FROM Accounts WHERE AccountId='guid-here')

tapi itu dijalankan dengan rencana di mana hasil dari Indeks Mencari Di Luar Dibawa dengan hasil dari Pemindaian Konstan dan kemudian dimasukkan ke dalam Compute Scalar:

SELECT <--- Compute Scalar <--- Left Outer Join <--- Constant Scan
                                      ^
                                      |------Clustered Index Seek

Apa itu sihir ekstra? Apa yang dilakukan Pemindaian Konstan diikuti oleh Left Outer Join?

sharptooth
sumber

Jawaban:

29

Semantik kedua pernyataan ini berbeda:

  • Yang pertama tidak menetapkan nilai variabel jika tidak ada baris yang ditemukan.
  • Yang kedua selalu menetapkan variabel, termasuk ke nol jika tidak ada baris yang ditemukan.

Pemindaian Konstan menghasilkan baris kosong (tanpa kolom!) Yang akan mengakibatkan variabel diperbarui jika tidak ada yang cocok dari tabel dasar. Gabung kiri memastikan baris kosong selamat dari gabung. Penugasan variabel dapat dianggap sebagai terjadi pada simpul akar dari rencana eksekusi.

Menggunakan SELECT @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result does not change
SELECT @result = AccountId 
FROM Accounts 
WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'};

SELECT @result;

Hasil 1

Menggunakan SET @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
(
    SELECT AccountId 
    FROM Accounts 
    WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'}
);

SELECT @result;

Hasil 2

Rencana eksekusi

Penugasan SELECTTidak ada baris yang tiba di simpul root, jadi tidak ada tugas yang terjadi.

SET penugasanBaris selalu tiba di simpul akar, sehingga penugasan variabel terjadi.


Pemindaian Konstanta tambahan dan Nested Loops Left Outer Join tidak perlu dikhawatirkan. Gabungan khususnya murah karena dijamin akan menemukan satu baris pada input luarnya, dan paling banyak satu baris (dalam contoh Anda) pada input dalam.

Ada cara lain untuk memastikan baris dihasilkan dari subquery untuk memastikan penugasan variabel terjadi. Salah satunya adalah dengan menggunakan agregat skalar redundan (tidak ada grup dengan klausa):

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
    (
        SELECT MAX(AccountId)
        FROM Accounts 
        WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'} 
    );
SELECT @result;

Hasil 3

Rencana eksekusi agregat skalar

Perhatikan agregat skalar menghasilkan baris meskipun tidak menerima input.

Dokumentasi:

Jika pernyataan SELECT tidak mengembalikan baris, variabel mempertahankan nilainya sekarang. Jika ekspresi adalah subquery skalar yang tidak mengembalikan nilai, variabel diatur ke NULL.

Untuk menetapkan variabel, kami sarankan Anda menggunakan SET @local_variable alih-alih SELECT @local_variable.

Bacaan lebih lanjut:

Paul White mengatakan GoFundMonica
sumber