Saya punya dua pertanyaan yang sangat mirip
Kueri pertama:
SELECT count(*)
FROM Audits a
JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
and a.TargetTypeId IN
(1,2,3,4,5,6,7,8,9,
11,12,13,14,15,16,17,18,19,
21,22,23,24,25,26,27,28,29,30,
31,32,33,34,35,36,37,38,39,
41,42,43,44,45,46,47,48,49,
51,52,53,54,55,56,57,58,59,
61,62,63,64,65,66,67,68,69,
71,72,73,74,75,76,77,78,79)
Hasil: 267479
Rencanakan: https://www.brentozar.com/pastetheplan/?id=BJWTtILyS
Kueri kedua:
SELECT count(*)
FROM Audits a
JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
and a.TargetTypeId IN
(1,2,3,4,5,6,7,8,9,
11,12,13,14,15,16,17,18,19,
21,22,23,24,25,26,27,28,29,
31,32,33,34,35,36,37,38,39,
41,42,43,44,45,46,47,48,49,
51,52,53,54,55,56,57,58,59,
61,62,63,64,65,66,67,68,69,
71,72,73,74,75,76,77,78,79)
Hasil: 25650
Rencanakan: https://www.brentozar.com/pastetheplan/?id=S1v79U8kS
Permintaan pertama membutuhkan waktu sekitar satu detik untuk diselesaikan, sedangkan permintaan kedua membutuhkan waktu sekitar 20 detik. Ini sepenuhnya kontra-intuitif bagi saya karena permintaan pertama memiliki jumlah yang jauh lebih tinggi daripada yang kedua. Ini ada di SQL server 2012
Mengapa ada begitu banyak perbedaan? Bagaimana saya bisa mempercepat permintaan kedua menjadi secepat yang pertama?
Berikut adalah skrip Buat tabel untuk kedua tabel:
CREATE TABLE [dbo].[AuditRelatedIds](
[AuditId] [bigint] NOT NULL,
[RelatedId] [uniqueidentifier] NOT NULL,
[AuditTargetTypeId] [smallint] NOT NULL,
CONSTRAINT [PK_AuditRelatedIds] PRIMARY KEY CLUSTERED
(
[AuditId] ASC,
[RelatedId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_AuditRelatedIdsRelatedId_INCLUDES] ON [dbo].[AuditRelatedIds]
(
[RelatedId] ASC
)
INCLUDE ( [AuditId]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
ALTER TABLE [dbo].[AuditRelatedIds] WITH CHECK ADD CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id] FOREIGN KEY([AuditId])
REFERENCES [dbo].[Audits] ([Id])
ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id]
ALTER TABLE [dbo].[AuditRelatedIds] WITH CHECK ADD CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([AuditTargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])
ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id]
CREATE TABLE [dbo].[Audits](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[TargetTypeId] [smallint] NOT NULL,
[TargetId] [nvarchar](40) NOT NULL,
[TargetName] [nvarchar](max) NOT NULL,
[Action] [tinyint] NOT NULL,
[ActionOverride] [tinyint] NULL,
[Date] [datetime] NOT NULL,
[UserDisplayName] [nvarchar](max) NOT NULL,
[DescriptionData] [nvarchar](max) NULL,
[IsNotification] [bit] NOT NULL,
CONSTRAINT [PK_Audits] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
SET ANSI_PADDING ON
CREATE NONCLUSTERED INDEX [IX_AuditsTargetId] ON [dbo].[Audits]
(
[TargetId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
SET ANSI_PADDING ON
CREATE NONCLUSTERED INDEX [IX_AuditsTargetTypeIdAction_INCLUDES] ON [dbo].[Audits]
(
[TargetTypeId] ASC,
[Action] ASC
)
INCLUDE ( [TargetId],
[UserDisplayName]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY]
ALTER TABLE [dbo].[Audits] WITH CHECK ADD CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([TargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])
ALTER TABLE [dbo].[Audits] CHECK CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id]
TargetTypeId = 30
? Tampaknya rencana berbeda karena nilai yang satu ini benar-benar membuat jumlah data (diharapkan akan) dikembalikan.Jawaban:
Tl; dr di bagian bawah
Mengapa rencana buruk itu dipilih
Alasan utama untuk memilih satu paket daripada yang lain adalah
Estimated total subtree
biayanya.Biaya ini lebih rendah untuk rencana yang buruk daripada untuk rencana yang berkinerja lebih baik.
Perkiraan total biaya subtree untuk rencana buruk:
Perkiraan biaya subtree total untuk rencana kinerja Anda yang lebih baik
Operator memperkirakan biaya
Operator tertentu dapat mengambil sebagian besar dari biaya ini, dan dapat menjadi alasan bagi pengoptimal untuk memilih jalur / rencana yang berbeda.
Dalam rencana kami yang berkinerja lebih baik, sebagian besar
Subtreecost
dihitung padaindex seek
&nested loops operator
melakukan gabungan:Sementara untuk rencana kueri buruk kami,
Clustered index seek
biaya operator lebih rendahYang seharusnya menjelaskan mengapa rencana lain bisa dipilih.
(Dan dengan menambahkan parameter
30
meningkatkan biaya rencana buruk di mana ia telah naik di atas871.510000
perkiraan biaya). Perkiraan menebak ™Rencana berkinerja lebih baik
Rencana yang buruk
Di mana ini membawa kita?
Informasi ini membawa kita ke suatu cara untuk memaksa rencana kueri buruk pada contoh kita (Lihat DML untuk hampir mereplikasi Masalah OP untuk data yang digunakan untuk mereplikasi masalah)
Dengan menambahkan
INNER LOOP JOIN
petunjuk bergabungItu lebih dekat, tetapi memiliki beberapa perbedaan urutan pesanan:
Menulis ulang
Upaya penulisan ulang pertama saya bisa menyimpan semua angka-angka ini di tabel temp sebagai gantinya:
Dan kemudian menambahkan
JOIN
bukan yang besarIN()
Paket kueri kami berbeda tetapi belum diperbaiki:
dengan perkiraan biaya operator yang sangat besar di atas
AuditRelatedIds
mejaDi sinilah saya perhatikan itu
Alasan mengapa saya tidak bisa langsung membuat ulang paket Anda adalah pemfilteran bitmap yang dioptimalkan.
Saya dapat membuat kembali rencana Anda dengan menonaktifkan filter bitmap yang dioptimalkan dengan menggunakan traceflags
7497
&7498
Informasi lebih lanjut tentang filter bitmap yang dioptimalkan di sini .
Ini berarti, bahwa tanpa filter bitmap, pengoptimal menganggap lebih baik untuk pertama kali bergabung ke
#number
tabel dan kemudian bergabung keAuditRelatedIds
tabel.Saat memaksa pesanan,
OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498, FORCE ORDER);
kita bisa melihat alasannya:&
Tidak baik
Menghapus kemampuan untuk berjalan paralel dengan maxdop 1
Saat menambahkan
MAXDOP 1
kueri berkinerja lebih cepat, utas tunggal.Dan menambahkan indeks ini
Saat menggunakan gabungan gabung.
Hal yang sama berlaku ketika kita menghapus petunjuk permintaan permintaan kekuatan atau tidak menggunakan tabel #Number dan menggunakan
IN()
sebaliknya.Saran saya adalah melihat ke dalam menambahkan
MAXDOP(1)
dan melihat apakah itu membantu permintaan Anda, dengan menulis ulang jika diperlukan.Tentu Anda juga harus ingat bahwa pada akhirnya saya berkinerja lebih baik karena penyaringan bitmap yang dioptimalkan & benar-benar menggunakan beberapa utas untuk efek yang baik:
TL; DR
Perkiraan biaya akan menentukan rencana yang dipilih, saya dapat mereplikasi perilaku dan melihat bahwa
optimized bitmap filters
+parallellism
operator mana yang ditambahkan di akhir saya untuk melakukan kueri dengan performa dan cara yang cepat.Anda bisa melihat menambahkan
MAXDOP(1)
ke permintaan Anda sebagai cara untuk mudah-mudahan mendapatkan hasil terkontrol yang sama setiap kali, denganmerge join
dan tidak 'buruk'parallellism
.Mengupgrade ke versi yang lebih baru dan menggunakan versi penduga kardinalitas yang lebih tinggi daripada yang
CardinalityEstimationModelVersion="70"
mungkin juga membantu.Tabel sementara angka untuk melakukan pemfilteran multi nilai juga dapat membantu.
DML hampir mereplikasi Masalah OP
Saya menghabiskan lebih banyak waktu untuk hal ini daripada yang ingin saya akui
sumber
MAXDOP 0
tampaknya telah memperbaikinya. Terima kasih banyak!Dari apa yang saya tahu perbedaan utama antara kedua rencana adalah perbedaan dalam apa yang dimaksud dengan "Filter Utama".
Dengan versi pertama, filter utama diturunkan yang
Audit.ID
terkait denganari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
lalu filter daftar itu ke orang-orang yangAudit.TargetTypeID
ada dalam daftar.Dengan versi kedua filter utama berasal yang
Audit.ID
terkait dengan daftarAudit.TargetTypeID
.Karena penambahan
Audit.TargetTypeID = 30
tampaknya secara dramatis meningkatkan jumlah catatan (masing-masing 267.479 dan 25.650 menurut Pertanyaan Asli). Itu mungkin mengapa rencana eksekusi berbeda. (Seperti yang saya mengerti) SQL akan mencoba melakukan fungsi yang paling selektif terlebih dahulu dan kemudian menerapkan aturan selanjutnya setelah itu. Dengan versi pertama, kueri olehAuditRelatedID.RelatedID
untuk kemudian menemukanAudit.ID
mungkin lebih selektif daripada mencoba menggunakanAudit.TargetTypeID
untuk kemudian menemukanAudit.ID
.Untuk kredit ypercube. Anda tentu saja dapat memperbarui
[AuditRelatedIds].[IX_AuditRelatedIdsRelatedId_INCLUDES]
untuk memiliki keduanyaRelatedID
danAuditID
sebagai bagian dari indeks bukannyaAuditID
sebagai bagian dariINCLUDE
. Seharusnya tidak mengambil ruang indeks tambahan dan akan memungkinkan Anda untuk menggunakan kedua kolom dalamJOIN
klausa. Itu dapat membantu Pengoptimal Kueri membuat rencana eksekusi yang sama untuk kedua kueri.Beroperasi dengan logika yang serupa, mungkin ada beberapa manfaat untuk indeks
Audit
yang berisiTargetTypeID ASC, ID ASC
pada node dipesan / filter aktual (bukan sebagai bagian dariINCLUDE
). Ini harus memungkinkan pengoptimal Kueri untuk memfilterAudit.TargetTypeID
saat itu dengan cepat bergabungAuditReferenceIds.AuditID
. Sekarang ini mungkin berakhir dengan kedua pertanyaan memilih paket yang kurang efisien jadi saya hanya akan mencobanya setelah mencoba rekomendasi ypercube.sumber