Ini adalah keputusan pengoptimal berbasis biaya.
Perkiraan biaya yang digunakan dalam pilihan ini tidak benar karena mengasumsikan kemandirian statistik antara nilai dalam kolom yang berbeda.
Ini mirip dengan masalah yang dijelaskan dalam Row Goals Gone Rogue di mana bilangan genap dan ganjil berkorelasi negatif.
Mudah direproduksi.
CREATE TABLE dbo.animal(
id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
colour varchar(50) NOT NULL,
species varchar(50) NOT NULL,
Filler char(10) NULL
);
/*Insert 20 million rows with 1% black and 1% swan but no black swans*/
WITH T
AS (SELECT TOP 20000000 ROW_NUMBER() OVER (ORDER BY @@SPID) AS RN
FROM master..spt_values v1,
master..spt_values v2,
master..spt_values v3)
INSERT INTO dbo.animal
(colour,
species)
SELECT CASE
WHEN RN % 100 = 1 THEN 'black'
ELSE CAST(RN % 100 AS VARCHAR(3))
END,
CASE
WHEN RN % 100 = 2 THEN 'swan'
ELSE CAST(RN % 100 AS VARCHAR(3))
END
FROM T
/*Create some indexes*/
CREATE NONCLUSTERED INDEX ix_species ON dbo.animal(species);
CREATE NONCLUSTERED INDEX ix_colour ON dbo.animal(colour);
Sekarang coba
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black'
AND species LIKE 'swan'
Ini memberikan rencana di bawah ini yang dihitung biayanya 0.0563167
.
Paket tersebut dapat melakukan gabungan gabungan antara hasil dari dua indeks pada id
kolom. ( Lebih detail dari algoritma gabungan gabung di sini ).
Gabung bergabung membutuhkan kedua input untuk dipesan oleh kunci bergabung.
Indeks nonclustered dipesan oleh (species, id)
dan (colour, id)
masing - masing (indeks nonunique non clustered selalu memiliki locator baris ditambahkan ke akhir kunci secara implisit jika tidak ditambahkan secara eksplisit). Permintaan tanpa wildcard melakukan pencarian kesetaraan ke species = 'swan'
dan colour ='black'
. Karena setiap pencarian hanya mengambil satu nilai persis dari kolom terkemuka, baris yang cocok akan dipesan oleh id
karena itu, rencana ini dimungkinkan.
Operator rencana kueri mengeksekusi dari kiri ke kanan . Dengan operator kiri meminta baris dari anak-anaknya, yang pada gilirannya meminta baris dari anak - anak mereka (dan seterusnya hingga simpul daun tercapai). The TOP
iterator akan berhenti meminta setiap baris lebih dari anaknya setelah 10 telah diterima.
SQL Server memiliki statistik pada indeks yang mengatakan bahwa 1% dari baris cocok dengan masing-masing predikat. Ini mengasumsikan bahwa statistik ini independen (yaitu tidak berkorelasi baik positif atau negatif) sehingga rata-rata setelah diproses 1.000 baris yang cocok dengan predikat pertama, akan ditemukan 10 yang cocok dengan yang kedua dan dapat keluar. (rencana di atas sebenarnya menunjukkan 987 daripada 1.000 tetapi cukup dekat).
Faktanya karena predikat berkorelasi negatif, rencana aktual menunjukkan bahwa semua 200.000 baris yang cocok perlu diproses dari masing-masing indeks tetapi ini dimitigasi sampai batas tertentu karena nol baris yang bergabung juga berarti nol pencarian benar-benar diperlukan.
Dibandingkan dengan
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
Yang memberikan rencana di bawah ini yang dihitung biayanya 0.567943
Penambahan wildcard trailing sekarang telah menyebabkan pemindaian indeks. Biaya paket ini masih cukup rendah meskipun untuk pemindaian pada tabel 20 juta baris.
Menambahkan querytraceon 9130
menunjukkan beberapa informasi lebih lanjut
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 9130)
Dapat dilihat bahwa SQL Server menganggap hanya perlu memindai sekitar 100.000 baris sebelum menemukan 10 yang cocok dengan predikat dan TOP
dapat berhenti meminta baris.
Sekali lagi ini masuk akal dengan asumsi independensi sebagai 10 * 100 * 100 = 100,000
Akhirnya mari kita coba dan paksa rencana persimpangan indeks
SELECT TOP 10 *
FROM animal WITH (INDEX(ix_species), INDEX(ix_colour))
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
Ini memberikan rencana paralel bagi saya dengan perkiraan biaya 3,4625
Perbedaan utama di sini adalah bahwa colour like 'black%'
predikat sekarang dapat cocok dengan berbagai warna. Ini berarti baris indeks yang cocok untuk predikat itu tidak lagi dijamin untuk disortir sesuai urutan id
.
Misalnya indeks pencarian like 'black%'
mungkin mengembalikan baris berikut
+------------+----+
| Colour | id |
+------------+----+
| black | 12 |
| black | 20 |
| black | 23 |
| black | 25 |
| blackberry | 1 |
| blackberry | 50 |
+------------+----+
Dalam setiap warna id akan dipesan tetapi id di berbagai warna mungkin tidak.
Akibatnya SQL Server tidak lagi dapat melakukan persimpangan indeks gabungan gabung (tanpa menambahkan operator pengurutan blokir) dan ia memilih untuk melakukan hash bergabung. Hash Join memblokir pada input build jadi sekarang biayanya mencerminkan fakta bahwa semua baris yang cocok perlu diproses dari input build daripada mengasumsikan hanya perlu memindai 1.000 seperti pada rencana pertama.
Input probe bukan pemblokiran dan masih salah memperkirakan bahwa ia akan dapat berhenti memeriksa setelah memproses 987 baris dari itu.
(Info lebih lanjut tentang Non-blocking vs iterator pemblokiran di sini)
Mengingat peningkatan biaya baris perkiraan tambahan dan hash bergabung dengan pemindaian clustered index parsial terlihat lebih murah.
Dalam praktiknya tentu saja pemindaian indeks berkerumun "parsial" tidak parsial sama sekali dan perlu dilakukan pemotongan keseluruhan 20 juta baris daripada 100 ribu yang diasumsikan ketika membandingkan rencana.
Meningkatkan nilai TOP
(atau menghapus seluruhnya) akhirnya menemui titik kritis di mana jumlah baris yang diperkirakan akan dipindai oleh pemindaian CI perlu membuat rencana itu terlihat lebih mahal dan kembali ke rencana persimpangan indeks. Bagi saya cut off point antara dua rencana adalah TOP (89)
vs TOP (90)
.
Untuk Anda mungkin berbeda karena tergantung seberapa lebar indeks berkerumun itu.
Menghapus TOP
dan memaksa pemindaian CI
SELECT *
FROM animal WITH (INDEX = 1)
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
Dibebankan pada 88.0586
pada mesin saya untuk tabel contoh saya.
Jika SQL Server sadar bahwa kebun binatang tidak memiliki angsa hitam dan perlu melakukan pemindaian penuh alih-alih hanya membaca 100.000 baris, rencana ini tidak akan dipilih.
Saya sudah mencoba statistik multi-kolom animal(species,colour)
dan animal(colour,species)
dan menyaring statistik, animal (colour) where species = 'swan'
tetapi tidak ada yang membantu meyakinkan bahwa angsa hitam tidak ada dan TOP 10
pemindaian perlu memproses lebih dari 100.000 baris.
Ini disebabkan oleh "asumsi inklusi" di mana SQL Server pada dasarnya mengasumsikan bahwa jika Anda mencari sesuatu itu mungkin ada.
Pada 2008+ ada flag jejak terdokumentasi 4138 yang mematikan tujuan baris. Efek dari ini adalah bahwa rencana dihitung biayanya tanpa asumsi bahwa TOP
akan memungkinkan operator anak untuk mengakhiri lebih awal tanpa membaca semua baris yang cocok. Dengan jejak bendera ini di tempat saya secara alami mendapatkan rencana persimpangan indeks yang lebih optimal.
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 4138)
Paket ini sekarang benar biaya untuk membaca 200 ribu baris penuh di kedua indeks mencari tetapi lebih dari biaya pencarian kunci (diperkirakan 2 ribu vs aktual 0. TOP 10
Akan membatasi ini hingga maksimum 10 tetapi bendera jejak mencegah ini diperhitungkan) . Meski demikian, paket ini biayanya jauh lebih murah daripada pemindaian CI penuh sehingga dipilih.
Tentu saja rencana ini mungkin tidak akan optimal untuk kombinasi yang sangat umum. Seperti angsa putih.
Indeks gabungan pada animal (colour, species)
atau idealnya animal (species, colour)
akan memungkinkan kueri menjadi jauh lebih efisien untuk kedua skenario.
Untuk menggunakan indeks komposit yang paling efisien, LIKE 'swan'
perlu diubah juga = 'swan'
.
Tabel di bawah ini menunjukkan predikat pencarian dan predikat residual yang ditunjukkan dalam rencana eksekusi untuk keempat permutasi.
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
| WHERE clause | Index | Seek Predicate | Residual Predicate |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
| colour LIKE 'black%' AND species LIKE 'swan' | ix_colour_species | colour >= 'black' AND colour < 'blacL' | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species LIKE 'swan' | ix_species_colour | species >= 'swan' AND species <= 'swan' | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species = 'swan' | ix_colour_species | (colour,species) >= ('black', 'swan')) AND colour < 'blacL' | colour LIKE 'black%' AND species = 'swan' |
| colour LIKE 'black%' AND species = 'swan' | ix_species_colour | species = 'swan' AND (colour >= 'black' and colour < 'blacL') | colour like 'black%' |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
TOP
nilai dalam suatu variabel berarti ia akan menganggapTOP 100
alih-alihTOP 10
. Ini mungkin atau mungkin tidak membantu tergantung pada apa titik kritis antara kedua paket itu.