Saya mencoba meningkatkan kueri (sub-) ini menjadi bagian dari kueri yang lebih besar:
select SUM(isnull(IP.Q, 0)) as Q,
IP.OPID
from IP
inner join I
on I.ID = IP.IID
where
IP.Deleted=0 and
(I.Status > 0 AND I.Status <= 19)
group by IP.OPID
Penjaga Rencana Explorer menunjukkan beberapa Pencarian Kunci yang relatif mahal untuk tabel dbo. [I] dilakukan oleh permintaan di atas.
Tabel dbo.I
CREATE TABLE [dbo].[I] (
[ID] UNIQUEIDENTIFIER NOT NULL,
[OID] UNIQUEIDENTIFIER NOT NULL,
[] UNIQUEIDENTIFIER NOT NULL,
[] UNIQUEIDENTIFIER NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NOT NULL,
[] CHAR (3) NOT NULL,
[] CHAR (3) DEFAULT ('EUR') NOT NULL,
[] DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
[] CHAR (10) NOT NULL,
[] DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
[] DATETIME DEFAULT (getdate()) NOT NULL,
[] VARCHAR (35) NULL,
[] NVARCHAR (100) NOT NULL,
[] NVARCHAR (100) NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[Status] INT DEFAULT ((0)) NOT NULL,
[] DECIMAL (18, 2) NOT NULL,
[] DECIMAL (18, 2) NOT NULL,
[] DECIMAL (18, 2) NOT NULL,
[] DATETIME DEFAULT (getdate()) NULL,
[] DATETIME NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] DATETIME NULL,
[] VARCHAR (50) NULL,
[] DATETIME DEFAULT (getdate()) NOT NULL,
[] VARCHAR (50) NOT NULL,
[] DATETIME NULL,
[] VARCHAR (50) NULL,
[] ROWVERSION NOT NULL,
[] DATETIME NULL,
[] INT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] NVARCHAR (50) NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] DECIMAL (18, 2) NULL,
[] DECIMAL (18, 2) NULL,
[] DECIMAL (18, 2) DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] DATETIME NULL,
[] DATETIME NULL,
[] VARCHAR (35) NULL,
[] DECIMAL (18, 2) DEFAULT ((0)) NOT NULL,
CONSTRAINT [PK_I] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
CONSTRAINT [FK_I_O] FOREIGN KEY ([OID]) REFERENCES [dbo].[O] ([ID]),
CONSTRAINT [FK_I_Status] FOREIGN KEY ([Status]) REFERENCES [dbo].[T_Status] ([Status])
);
GO
CREATE CLUSTERED INDEX [CIX_Invoice]
ON [dbo].[I]([OID] ASC) WITH (FILLFACTOR = 90);
Tabel dbo.IP
CREATE TABLE [dbo].[IP] (
[ID] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
[IID] UNIQUEIDENTIFIER NOT NULL,
[OID] UNIQUEIDENTIFIER NOT NULL,
[Deleted] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[]UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] INT NOT NULL,
[] VARCHAR (35) NULL,
[] NVARCHAR (100) NOT NULL,
[] NTEXT NULL,
[] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
[] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
[] DECIMAL (4, 2) NOT NULL,
[] INT DEFAULT ((1)) NOT NULL,
[] DATETIME DEFAULT (getdate()) NOT NULL,
[] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[] DATETIME NULL,
[] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[] ROWVERSION NOT NULL,
[] INT DEFAULT ((1)) NOT NULL,
[] DATETIME NULL,
[] UNIQUEIDENTIFIER NULL,
[] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
[] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
[] INT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[]UNIQUEIDENTIFIER NULL,
[]NVARCHAR (35) NULL,
[] VARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] UNIQUEIDENTIFIER NULL,
[] VARCHAR (12) NULL,
[] VARCHAR (4) NULL,
[] NVARCHAR (50) NULL,
[] NVARCHAR (50) NULL,
[] VARCHAR (35) NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] NVARCHAR (50) NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] DECIMAL (18, 2) NULL,
[]TINYINT DEFAULT ((1)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((1)) NOT NULL,
CONSTRAINT [PK_IP] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
CONSTRAINT [FK_IP_I] FOREIGN KEY ([IID]) REFERENCES [dbo].[I] ([ID]) ON DELETE CASCADE NOT FOR REPLICATION,
CONSTRAINT [FK_IP_XType] FOREIGN KEY ([XType]) REFERENCES [dbo].[xTYPE] ([Value]) NOT FOR REPLICATION
);
GO
CREATE CLUSTERED INDEX [IX_IP_CLUST]
ON [dbo].[IP]([IID] ASC) WITH (FILLFACTOR = 90);
Tabel "I" memiliki sekitar 100.000 baris, indeks berkerumun memiliki 9.386 halaman.
Tabel IP adalah "anak" - tabel I dan memiliki sekitar 175.000 baris.
Saya mencoba menambahkan indeks baru mengikuti aturan urutan kolom indeks: "WHERE-JOIN-ORDER- (SELECT)"
untuk mengatasi pencarian kunci dan membuat pencarian indeks:
CREATE NONCLUSTERED INDEX [IX_I_Status_1]
ON [dbo].[Invoice]([Status], [ID])
Permintaan yang diekstrak segera menggunakan indeks ini. Tetapi permintaan yang lebih besar asli itu adalah bagian dari, tidak. Bahkan tidak menggunakannya ketika saya memaksanya untuk menggunakan WITH (INDEX (IX_I_Status_1)).
Setelah beberapa saat, saya memutuskan untuk mencoba indeks baru lainnya dan mengubah urutan kolom yang diindeks:
CREATE NONCLUSTERED INDEX [IX_I_Status_2]
ON [dbo].[Invoice]([ID], [Status])
WOHA! Indeks ini digunakan oleh kueri yang diekstraksi dan juga oleh kueri yang lebih besar!
Kemudian saya membandingkan statistik IO yang diekstraksi dengan memaksanya untuk menggunakan [IX_I_Status_1] dan [IX_I_Status_2]:
Hasil [IX_I_Status_1]:
Table 'I'. Scan count 5, logical reads 636, physical reads 16, read-ahead reads 574
Table 'IP'. Scan count 5, logical reads 1134, physical reads 11, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0
Hasil [IX_I_Status_2]:
Table 'I'. Scan count 1, logical reads 615, physical reads 6, read-ahead reads 631
Table 'IP'. Scan count 1, logical reads 1024, physical reads 5, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0
OK, saya bisa mengerti bahwa permintaan mega-besar-monster mungkin terlalu kompleks untuk membuat SQL server menangkap rencana eksekusi yang ideal dan mungkin kehilangan indeks baru saya. Tapi saya tidak mengerti mengapa indeks [IX_I_Status_2] tampaknya lebih cocok dan lebih efisien untuk kueri.
Karena kueri pertama-tama dari tabel filter I oleh kolom STATUS dan kemudian bergabung dengan tabel IP, saya tidak mengerti mengapa [IX_I_Status_2] lebih baik dan digunakan oleh Sql Server daripada [IX_I_Status_1]?
Jawaban:
Setidaknya itu adalah saran yang tidak lengkap dan berpotensi menyesatkan (saya tidak repot-repot membaca seluruh artikel). Jika Anda akan membaca hal-hal di Internet (termasuk ini), Anda harus menyesuaikan jumlah kepercayaan Anda sesuai dengan seberapa baik Anda sudah tahu dan percaya pada penulis, tetapi selalu kemudian verifikasi untuk diri Anda sendiri.
Ada sejumlah "aturan praktis" untuk membuat indeks, tergantung pada skenario yang tepat, tetapi tidak ada yang benar-benar pengganti yang baik untuk memahami masalah inti untuk diri sendiri. Baca tentang implementasi indeks dan operator rencana eksekusi di SQL Server, ikuti beberapa latihan, dan dapatkan pemahaman yang baik tentang bagaimana indeks dapat digunakan untuk membuat rencana eksekusi lebih efisien. Tidak ada jalan pintas yang efektif untuk mencapai pengetahuan dan pengalaman ini.
Secara umum, saya dapat mengatakan bahwa indeks Anda paling sering memiliki kolom yang digunakan untuk tes kesetaraan terlebih dahulu, dengan ketidaksetaraan yang terakhir, dan / atau disediakan oleh filter pada indeks. Ini bukan pernyataan lengkap, karena indeks juga dapat memberikan pesanan, yang mungkin lebih berguna daripada mencari langsung ke satu atau lebih kunci dalam beberapa situasi. Misalnya, pemesanan dapat digunakan untuk menghindari pengurutan, untuk mengurangi biaya opsi penggabungan fisik seperti penggabungan gabungan, untuk mengaktifkan agregat aliran, menemukan beberapa baris kualifikasi pertama dengan cepat ... dan seterusnya.
Saya menjadi sedikit kabur di sini, karena memilih indeks yang ideal untuk permintaan tergantung pada banyak faktor - ini adalah topik yang sangat luas.
Bagaimanapun, bukan hal yang aneh untuk menemukan sinyal yang saling bertentangan untuk indeks 'terbaik' dalam kueri. Misalnya, predikat gabungan Anda ingin baris dipesan satu cara untuk gabungan bergabung, grup dengan ingin baris diurutkan cara lain untuk agregat aliran, dan menemukan baris yang memenuhi syarat menggunakan predikat klausa mana yang akan menyarankan indeks lainnya.
Alasan pengindeksan adalah seni dan juga sains adalah bahwa kombinasi yang ideal tidak selalu memungkinkan secara logis. Memilih indeks kompromi terbaik untuk beban kerja (bukan hanya satu permintaan) membutuhkan keterampilan analitik, pengalaman, dan pengetahuan khusus sistem. Jika mudah , alat otomatis akan menjadi sempurna, dan konsultan penyesuaian kinerja akan jauh lebih sedikit dalam permintaan.
Sejauh menyangkut saran indeks yang hilang: ini adalah oportunistik. Pengoptimal membawa mereka ke perhatian Anda ketika mencoba untuk mencocokkan predikat dan urutan pengurutan yang diperlukan untuk indeks yang tidak ada. Saran karena itu didasarkan pada upaya pencocokan tertentu dalam konteks spesifik dari variasi sub-rencana tertentu yang sedang dipertimbangkan pada saat itu.
Dalam konteks, saran selalu masuk akal, dalam hal mengurangi perkiraan biaya akses data, sesuai dengan model pengoptimal. Itu tidak melakukan analisis yang lebih luas dari permintaan secara keseluruhan (apalagi beban kerja yang lebih luas), jadi Anda harus menganggap saran ini sebagai petunjuk lembut bahwa orang yang terampil perlu melihat indeks yang tersedia, dengan saran sebagai permulaan point (dan biasanya tidak lebih dari itu).
Dalam kasus Anda,
(Status) INCLUDE (ID)
saran mungkin muncul ketika sedang melihat kemungkinan bergabung hash atau bergabung (contoh nanti). Dalam konteks yang sempit itu, saran itu masuk akal. Untuk kueri secara keseluruhan, mungkin tidak. Indeks(ID, Status)
memungkinkan loop bersarang bergabung denganID
sebagai referensi luar: pencarian kesetaraanID
dan ketidaksetaraanStatus
per iterasi.Salah satu pilihan indeks yang mungkin adalah:
... yang menghasilkan rencana seperti:
Saya tidak mengatakan indeks ini optimal untuk Anda; mereka kebetulan bekerja untuk menghasilkan rencana yang tampak masuk akal bagi saya, tanpa bisa melihat statistik untuk tabel yang terlibat, atau definisi lengkap dan pengindeksan yang ada. Juga, saya tidak tahu apa-apa tentang beban kerja yang lebih luas atau permintaan nyata.
Atau (hanya untuk menunjukkan salah satu dari banyak kemungkinan tambahan):
Memberi:
Rencana eksekusi dibuat menggunakan SQL Sentry Plan Explorer .
sumber