Mengapa tabel temp merupakan solusi yang lebih efisien untuk Masalah Halloween daripada spool yang bersemangat?


14

Pertimbangkan kueri berikut yang menyisipkan baris dari tabel sumber hanya jika mereka belum ada di tabel target:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

Satu bentuk rencana yang mungkin termasuk gabungan gabung dan spool bersemangat. Operator spool bersemangat hadir untuk memecahkan Masalah Halloween :

rencana pertama

Di komputer saya, kode di atas dijalankan sekitar 6900 ms. Kode repro untuk membuat tabel disertakan di bagian bawah pertanyaan. Jika saya tidak puas dengan kinerja, saya mungkin akan mencoba memuat baris yang akan dimasukkan ke dalam tabel temp bukannya mengandalkan eool spool. Inilah satu kemungkinan implementasi:

DROP TABLE IF EXISTS #CONSULTANT_RECOMMENDED_TEMP_TABLE;
CREATE TABLE #CONSULTANT_RECOMMENDED_TEMP_TABLE (
    ID BIGINT,
    PRIMARY KEY (ID)
);

INSERT INTO #CONSULTANT_RECOMMENDED_TEMP_TABLE WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1);

Kode baru dijalankan sekitar 4400 ms. Saya bisa mendapatkan rencana aktual dan menggunakan Statistik Waktu Aktual ™ untuk memeriksa di mana waktu dihabiskan di tingkat operator. Perhatikan bahwa meminta paket aktual menambah overhead signifikan untuk kueri ini sehingga total tidak akan cocok dengan hasil sebelumnya.

╔═════════════╦═════════════╦══════════════╗
  operator    first query  second query 
╠═════════════╬═════════════╬══════════════╣
 big scan     1771         1744         
 little scan  163          166          
 sort         531          530          
 merge join   709          669          
 spool        3202         N/A          
 temp insert  N/A          422          
 temp scan    N/A          187          
 insert       3122         1545         
╚═════════════╩═════════════╩══════════════╝

Paket kueri dengan spool eager tampaknya menghabiskan lebih banyak waktu secara signifikan pada operator insert dan spool dibandingkan dengan paket yang menggunakan tabel temp.

Mengapa rencana dengan tabel temp lebih efisien? Bukankah spool yang bersemangat kebanyakan hanya meja temp internal? Saya percaya saya mencari jawaban yang fokus pada internal. Saya dapat melihat bagaimana tumpukan panggilan berbeda tetapi tidak dapat memahami gambaran besarnya.

Saya menggunakan SQL Server 2017 CU 11 jika seseorang ingin tahu. Berikut ini adalah kode untuk mengisi tabel yang digunakan dalam permintaan di atas:

DROP TABLE IF EXISTS dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR;

CREATE TABLE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR (
ID BIGINT NOT NULL,
PRIMARY KEY (ID)
);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (20000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.A_HEAP_OF_MOSTLY_NEW_ROWS;

CREATE TABLE dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (
ID BIGINT NOT NULL
);

INSERT INTO dbo.A_HEAP_OF_MOSTLY_NEW_ROWS WITH (TABLOCK)
SELECT TOP (1900000) 19999999 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Jawaban:


14

Inilah yang saya sebut Manual Halloween Protection .

Anda dapat menemukan contoh yang digunakan dengan pernyataan pembaruan di artikel saya Mengoptimalkan Permintaan Pembaruan . Kita harus sedikit berhati-hati untuk mempertahankan semantik yang sama, misalnya dengan mengunci tabel target terhadap semua modifikasi bersamaan saat query terpisah dijalankan, jika itu relevan dalam skenario Anda.

Mengapa rencana dengan tabel temp lebih efisien? Bukankah spool yang bersemangat kebanyakan hanya meja temp internal?

Kumparan memiliki beberapa karakteristik tabel sementara, tetapi keduanya tidak sama persis. Secara khusus, spool pada dasarnya adalah insert baris demi baris yang tidak berurutan ke struktur b-tree . Memang mendapat manfaat dari penguncian dan pendataan optimasi, tetapi tidak mendukung optimisasi beban curah .

Sebagai akibatnya, seseorang sering dapat memperoleh kinerja yang lebih baik dengan memecah kueri dengan cara alami: Massal memuat baris baru ke tabel atau variabel sementara, kemudian melakukan penyisipan yang dioptimalkan (tanpa perlindungan Halloween eksplisit) dari objek sementara.

Membuat pemisahan ini juga memungkinkan Anda kebebasan ekstra untuk menyetel bagian baca dan tulis dari pernyataan asli secara terpisah.

Sebagai catatan tambahan, menarik untuk memikirkan tentang bagaimana Masalah Halloween dapat diatasi menggunakan versi baris. Mungkin versi SQL Server di masa depan akan menyediakan fitur itu dalam keadaan yang sesuai.


Seperti yang disinggung oleh Michael Kutz dalam komentar, Anda juga dapat menjelajahi kemungkinan mengeksploitasi optimisasi pengisian lubang untuk menghindari HP eksplisit. Salah satu cara untuk mencapai ini untuk demo adalah dengan membuat indeks unik (berkerumun jika Anda suka) di IDkolom A_HEAP_OF_MOSTLY_NEW_ROWS.

CREATE UNIQUE INDEX i ON dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (ID);

Dengan jaminan itu, pengoptimal dapat menggunakan pengisian lubang dan berbagi rowset:

MERGE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (SERIALIZABLE) AS HICETY
USING dbo.A_HEAP_OF_MOSTLY_NEW_ROWS AS AHOMNR
    ON AHOMNR.ID = HICETY.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID) VALUES (AHOMNR.ID);

Rencana MERGE

Meskipun menarik, Anda masih dapat mencapai kinerja yang lebih baik dalam banyak kasus dengan menggunakan Perlindungan Halloween Manual yang diimplementasikan dengan hati-hati.


5

Untuk sedikit memperluas jawaban Paul, bagian dari perbedaan dalam waktu yang telah berlalu antara spool dan temp table mendekati tampaknya karena kurangnya dukungan untuk DML Request Sortopsi dalam spool plan. Dengan flag jejak tidak berdokumen 8795, waktu yang telah berlalu untuk pendekatan tabel temp melompat dari 4400 ms menjadi 5600 ms.

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1, QUERYTRACEON 8795);

Perhatikan bahwa ini tidak persis sama dengan insert yang dilakukan oleh spool plan. Kueri ini menulis lebih banyak data secara signifikan ke log transaksi.

Efek yang sama dapat dilihat secara terbalik dengan beberapa tipu daya. Dimungkinkan untuk mendorong SQL Server untuk menggunakan semacam bukannya gulungan untuk Perlindungan Halloween. Satu implementasi:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (987654321) 
maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
ORDER BY maybe_new_rows.ID, maybe_new_rows.ID + 1
OPTION (MAXDOP 1, QUERYTRACEON 7470, MERGE JOIN);

Sekarang rencananya memiliki operator TOP N Sortir di tempat spool. Penyortiran adalah operator pemblokiran sehingga spool tidak lagi diperlukan:

masukkan deskripsi gambar di sini

Lebih penting lagi, kami sekarang memiliki dukungan untuk DML Request Sortopsi tersebut. Melihat Statistik Waktu Aktual lagi, operator insert sekarang hanya membutuhkan 1623 ms. Keseluruhan paket membutuhkan sekitar 5400 ms untuk dijalankan tanpa meminta paket yang sebenarnya.

Seperti yang dijelaskan Hugo , operator Eager Spool tidak melakukan pemesanan. Itu paling mudah dilihat dengan TOP PERCENTrencana. Sangat disayangkan bahwa permintaan asli dengan spool tidak dapat mengambil keuntungan lebih baik dari sifat data yang diurutkan dalam spool.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.