Rencana eksekusi Anda
Saat melihat rencana kueri, kita dapat melihat bahwa satu indeks disentuh untuk melayani dua operasi filter.
Sederhananya, karena operator TOP, tujuan baris ditetapkan. Lebih banyak informasi & prasyarat tujuan baris dapat ditemukan di sini
Dari sumber yang sama:
Strategi sasaran baris pada umumnya berarti mendukung operasi navigasi yang tidak menghalangi (misalnya, sambungan loop bersarang, indeks pencarian, dan pencarian) lebih dari memblokir, operasi berbasis set seperti menyortir dan hashing. Ini bisa berguna kapan saja klien dapat memperoleh manfaat dari start-up yang cepat dan aliran yang stabil (dengan mungkin waktu eksekusi keseluruhan yang lebih panjang - lihat posting Rob Farley di atas). Ada juga penggunaan yang lebih jelas dan tradisional misalnya dalam menyajikan hasil halaman pada suatu waktu.
Seluruh tabel akan diselidiki ke dalam filter dengan menggunakan setengah bergabung kiri yang memiliki sasaran baris, berharap untuk mengembalikan 5 baris secepat dan seefisien mungkin.
Ini tidak terjadi, menghasilkan banyak iterasi atas .Fulltextmatch TVF.
Rekreasi
Berdasarkan rencana Anda , saya dapat membuat kembali masalah Anda:
CREATE TABLE dbo.Person(id int not null,lastname varchar(max));
CREATE UNIQUE INDEX ui_id ON dbo.Person(id)
CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON dbo.Person(lastname)
KEY INDEX ui_id
WITH STOPLIST = SYSTEM;
GO
INSERT INTO dbo.Person(id,lastname)
SELECT top(12000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
REPLICATE(CAST('A' as nvarchar(max)),80000)+ CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as varchar(10))
FROM master..spt_values spt1
CROSS APPLY master..spt_values spt2;
CREATE CLUSTERED INDEX cx_Id on dbo.Person(id);
Menjalankan kueri
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 1 OR contains("lastName", '"B*"');
Hasil menjadi paket permintaan yang sebanding dengan milik Anda:
Dalam contoh di atas, B tidak ada dalam indeks teks lengkap. Sebagai hasilnya tergantung pada parameter & data seberapa efisiennya rencana kueri.
Penjelasan yang lebih baik tentang ini dapat ditemukan di Row Goals, Bagian 2: Semi Joins oleh Paul White
... Dengan kata lain, pada setiap iterasi dari suatu aplikasi, kita dapat berhenti melihat input B segera setelah kecocokan pertama ditemukan, menggunakan predikat join push-down. Ini adalah hal yang tepat untuk tujuan baris: menghasilkan bagian dari rencana yang dioptimalkan untuk mengembalikan n baris pencocokan pertama dengan cepat (di mana n = 1 di sini).
Misalnya, mengubah predikat sehingga hasilnya lebih cepat ditemukan (di awal pemindaian).
select top (5) *
from dbo.Person
where "id" = 124
or contains("lastName", '"A*"');
yang where "id" = 124
akan dihilangkan karena indeks predikat fulltext sudah kembali 5 baris, memuaskan TOP()
predikat.
Hasilnya menunjukkan ini juga
id lastname
1 'AAA...'
2 'AAA...'
3 'AAA...'
4 'AAA...'
5 'AAA...'
Dan eksekusi TVF:
Memasukkan beberapa baris baru
INSERT INTO dbo.Person
SELECT 12001, REPLICATE(CAST('B' as nvarchar(max)),80000);
INSERT INTO dbo.Person
SELECT 12002, REPLICATE(CAST('B' as nvarchar(max)),80000);
Menjalankan kueri untuk menemukan baris yang dimasukkan sebelumnya ini
SELECT TOP (2) *
from dbo.Person
where "id" = 1
or contains("lastName", '"B*"');
Sekali lagi ini menghasilkan terlalu banyak iterasi pada hampir semua baris untuk mengembalikan nilai terakhir tetapi satu yang ditemukan.
id lastname
1 'AAA...'
12001 'BBB...'
Menyelesaikan
Saat menghapus sasaran baris dengan menggunakan traceflag 4138
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 124
OR contains("lastName", '"B*"')
OPTION(QUERYTRACEON 4138 );
Pengoptimal menggunakan pola bergabung lebih dekat ke menerapkan UNION
, dalam kasus kami ini menguntungkan karena mendorong predikat ke masing-masing indeks pencarian berkerumun, dan tidak menggunakan baris yang menyatu dengan operator setengah bergabung kiri.
Cara lain untuk menulis ini, tanpa menggunakan traceflag yang disebutkan di atas:
SELECT top (5) *
FROM
(
SELECT *
FROM dbo.Person
WHERE "id" = 1
UNION
SELECT *
FROM dbo.Person
WHERE contains("lastName", '"B*"')
) as A;
Dengan rencana kueri yang dihasilkan:
di mana fungsi teks lengkap diterapkan secara langsung
Sebagai sidenote, untuk op, kueri hotfix optimizer traceflag 4199 menyelesaikan masalahnya. Dia menerapkan ini dengan menambahkan OPTION(QUERYTRACEON(4199))
kueri. Saya tidak dapat mereproduksi perilaku itu di pihak saya. Perbaikan terbaru ini mengandung pengoptimalan setengah bergabung:
Bendera Jejak: 4102 Fungsi: SQL 9 - Kinerja kueri lambat jika rencana pelaksanaan kueri berisi operator semi join. Biasanya, operator semi join dihasilkan ketika kueri berisi kata kunci IN atau kata kunci EXISTS. Aktifkan flag 4102 dan 4118 untuk mengatasinya.
Sumber
Tambahan
Selama pengoptimalan berbasis biaya, pengoptimal juga dapat menambahkan spool indeks ke rencana eksekusi, dilaksanakan oleh LogOp_Spool Index on fly Eager
(atau mitra fisik)
Ini dilakukan dengan dataset saya untuk TOP(3)
tetapi tidak untukTOP(2)
SELECT TOP (3) *
from dbo.Physician
where "id" = 1
or contains("lastName", '"B*"')
Pada eksekusi pertama, spool yang bersemangat membaca dan menyimpan seluruh input sebelum mengembalikan subset baris yang diminta oleh eksekusi Predicate Later membaca dan mengembalikan subset baris yang sama atau berbeda dari meja kerja, tanpa harus mengeksekusi anak node lagi.
Sumber
Dengan predikat pencarian yang diterapkan pada spool eager indeks ini: