INSERT baris tunggal ... PILIH jauh lebih lambat daripada PILIH terpisah


18

Diberikan tabel tumpukan berikut dengan 400 baris bernomor 1 hingga 400:

DROP TABLE IF EXISTS dbo.N;
GO
SELECT 
    SV.number
INTO dbo.N 
FROM master.dbo.spt_values AS SV
WHERE 
    SV.[type] = N'P'
    AND SV.number BETWEEN 1 AND 400;

dan pengaturan berikut:

SET NOCOUNT ON;
SET STATISTICS IO, TIME OFF;
SET STATISTICS XML OFF;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

SELECTPernyataan berikut selesai dalam waktu sekitar 6 detik ( demo , paket ):

DECLARE @n integer = 400;

SELECT
    c = COUNT_BIG(*) 
FROM dbo.N AS N
CROSS JOIN dbo.N AS N2
CROSS JOIN dbo.N AS N3
WHERE 
    N.number <= @n
    AND N2.number <= @n
    AND N3.number <= @n
OPTION
    (OPTIMIZE FOR (@n = 1));

Catatan: OPTIMIZE FORKlausa @ hanya demi menghasilkan repro berukuran masuk akal yang menangkap detail penting dari masalah nyata, termasuk kardinalitas salah menaksir yang dapat muncul karena berbagai alasan.

Ketika output baris tunggal ditulis ke tabel, dibutuhkan 19 detik ( demo , paket ):

DECLARE @T table (c bigint NOT NULL);

DECLARE @n integer = 400;

INSERT @T
    (c)
SELECT
    c = COUNT_BIG(*) 
FROM dbo.N AS N
CROSS JOIN dbo.N AS N2
CROSS JOIN dbo.N AS N3
WHERE 
    N.number <= @n
    AND N2.number <= @n
    AND N3.number <= @n
OPTION
    (OPTIMIZE FOR (@n = 1));

Rencana pelaksanaan tampak identik selain dari memasukkan satu baris.

Semua waktu ekstra tampaknya dikonsumsi oleh penggunaan CPU.

Mengapa INSERTpernyataan itu jauh lebih lambat?

Jawaban:


21

SQL Server memilih untuk memindai tabel tumpukan di sisi dalam loop bergabung menggunakan kunci tingkat baris. Pemindaian penuh biasanya akan memilih penguncian tingkat halaman, tetapi kombinasi ukuran tabel dan predikat berarti mesin penyimpanan memilih kunci baris, karena itu tampaknya merupakan strategi termurah.

Misestimasi kardinalitas sengaja diperkenalkan dengan OPTIMIZE FORcara bahwa tumpukan dipindai lebih banyak dari yang diharapkan oleh pengoptimal, dan itu tidak menghasilkan gulungan seperti biasanya.

Kombinasi faktor ini berarti kinerja sangat sensitif terhadap jumlah kunci yang diperlukan saat runtime.

The SELECTmanfaat pernyataan dari optimasi yang memungkinkan tingkat-baris bersama kunci akan dilewati (mengambil hanya kunci halaman-tingkat niat-berbagi) bila tidak ada bahaya membaca data uncommitted, dan tidak ada data off-baris.

The INSERT...SELECTpernyataan tidak mendapatkan keuntungan dari optimasi ini, sehingga jutaan kunci RID diambil dan dirilis setiap detik dalam kasus kedua, bersama dengan kunci halaman-tingkat niat-bersama.

Sejumlah besar aktivitas penguncian menyumbang CPU ekstra dan waktu yang telah berlalu.

Solusi paling alami adalah memastikan optimizer (dan mesin penyimpanan) mendapatkan perkiraan kardinalitas yang layak sehingga mereka dapat membuat pilihan yang baik.

Jika itu tidak praktis dalam kasus penggunaan nyata, INSERTdan SELECTpernyataan dapat dipisahkan, dengan hasil yang SELECTdisimpan dalam variabel. Ini akan memungkinkan SELECTpernyataan mengambil manfaat dari optimasi penguncian-kunci.

Mengubah level isolasi juga dapat dilakukan untuk bekerja, baik dengan tidak mengambil kunci bersama, atau dengan memastikan bahwa eskalasi kunci terjadi dengan cepat.

Sebagai titik akhir yang menarik, kueri dapat dibuat untuk berjalan lebih cepat daripada SELECTkasus yang dioptimalkan dengan memaksa penggunaan gulungan menggunakan bendera jejak tidak berdokumen 8691.

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.