Pertanyaan yang sangat mirip, kinerja yang sangat berbeda

9

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]
Chocoman
sumber
3
Apakah kita bisa mendapatkan beberapa skema tabel dan rincian indeks. Seperti yang saya yakin Anda perhatikan rencana sedikit berbeda tetapi ternyata itu membuat perbedaan besar. Jika kita bisa mendapatkan detail itu daripada mungkin kita bisa melihat opsi apa yang kita miliki.
Kirk Saunders
2
Sebagai tip yang sangat cepat, alih-alih menggunakan IN, buat TempTable dengan kolom TINYINT / INT tunggal (berkerumun) dengan angka yang Anda inginkan, lalu INNER BERGABUNG dengannya. Selain itu kita kemungkinan akan membutuhkan informasi DDL seperti @KirkSaunders yang disebutkan di atas
George.Palacios
2
Apakah ada sesuatu yang spesial TargetTypeId = 30? Tampaknya rencana berbeda karena nilai yang satu ini benar-benar membuat jumlah data (diharapkan akan) dikembalikan.
Aaron Bertrand
Saya menyadari ini sangat luar biasa tetapi pernyataan "permintaan pertama menghasilkan lebih banyak baris daripada yang kedua." tidak benar. Keduanya mengembalikan 1 baris;)
ypercubeᵀᴹ
1
Saya memperbarui pertanyaan dengan membuat tabel pernyataan untuk kedua tabel
Chocoman

Jawaban:

8

Tl; dr di bagian bawah

Mengapa rencana buruk itu dipilih

Alasan utama untuk memilih satu paket daripada yang lain adalah Estimated total subtreebiayanya.

Biaya ini lebih rendah untuk rencana yang buruk daripada untuk rencana yang berkinerja lebih baik.

Perkiraan total biaya subtree untuk rencana buruk:

masukkan deskripsi gambar di sini

Perkiraan biaya subtree total untuk rencana kinerja Anda yang lebih baik

masukkan deskripsi gambar di sini


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 Subtreecostdihitung pada index seek& nested loops operatormelakukan gabungan:

masukkan deskripsi gambar di sini

Sementara untuk rencana kueri buruk kami, Clustered index seekbiaya operator lebih rendah

masukkan deskripsi gambar di sini

Yang seharusnya menjelaskan mengapa rencana lain bisa dipilih.

(Dan dengan menambahkan parameter 30meningkatkan biaya rencana buruk di mana ia telah naik di atas 871.510000perkiraan biaya). Perkiraan menebak ™

Rencana berkinerja lebih baik

masukkan deskripsi gambar di sini

Rencana yang buruk

masukkan deskripsi gambar di sini


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 JOINpetunjuk bergabung

SELECT count(*)
FROM Audits a
   INNER LOOP 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)

Itu lebih dekat, tetapi memiliki beberapa perbedaan urutan pesanan:

masukkan deskripsi gambar di sini


Menulis ulang

Upaya penulisan ulang pertama saya bisa menyimpan semua angka-angka ini di tabel temp sebagai gantinya:

CREATE TABLE #Numbers(Numbering INT)
INSERT INTO #Numbers(Numbering)
VALUES
(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);

Dan kemudian menambahkan JOINbukan yang besarIN()

SELECT count(*)
FROM Audits a
   INNER LOOP JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
   INNER JOIN #Numbers
   ON Numbering = a.TargetTypeId
WHERE 
    ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1';

Paket kueri kami berbeda tetapi belum diperbaiki:

masukkan deskripsi gambar di sini

dengan perkiraan biaya operator yang sangat besar di atas AuditRelatedIdsmeja

masukkan deskripsi gambar di sini


Di 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

SELECT count(*)
FROM Audits a 
   INNER JOIN AuditRelatedIds  ari ON a.Id = ari.AuditId 
   INNER JOIN #Numbers
   ON Numbering = a.TargetTypeId
WHERE 
    ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498);

Informasi lebih lanjut tentang filter bitmap yang dioptimalkan di sini .

masukkan deskripsi gambar di sini

Ini berarti, bahwa tanpa filter bitmap, pengoptimal menganggap lebih baik untuk pertama kali bergabung ke #numbertabel dan kemudian bergabung ke AuditRelatedIdstabel.

Saat memaksa pesanan, OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498, FORCE ORDER);kita bisa melihat alasannya:

masukkan deskripsi gambar di sini

& masukkan deskripsi gambar di sini

Tidak baik


Menghapus kemampuan untuk berjalan paralel dengan maxdop 1

Saat menambahkan MAXDOP 1kueri berkinerja lebih cepat, utas tunggal.

Dan menambahkan indeks ini

CREATE NONCLUSTERED INDEX [IX_AuditRelatedIdsRelatedId_AuditId] ON [dbo].[AuditRelatedIds]
(
    [RelatedId] ASC,
    [AuditId] 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];

masukkan deskripsi gambar di sini

Saat menggunakan gabungan gabung. masukkan deskripsi gambar di sini

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:

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini


TL; DR

Perkiraan biaya akan menentukan rencana yang dipilih, saya dapat mereplikasi perilaku dan melihat bahwa optimized bitmap filters+ parallellismoperator 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, dengan merge joindan 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

set NOCOUNT ON;
DECLARE @I INT = 0
WHILE @I < 56
BEGIN
INSERT INTO  [dbo].[Audits] WITH(TABLOCK) 
([TargetTypeId],
    [TargetId],
    [TargetName],
    [Action],
    [ActionOverride] ,
    [Date] ,
    [UserDisplayName],
    [DescriptionData],
    [IsNotification]) 
SELECT top(500000) CASE WHEN ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 10000 = 30 then 29 ELSE ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 10000 END as rownum2 -- TILL 50 and no 30
,'bla','bla2',1,1,getdate(),'bla3','Bla4',1
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2;
SET @I +=1;
END

-- 'Bad Query matches'
INSERT INTO  [dbo].[AuditRelatedIds] WITH(TABLOCK)
    ([AuditId] ,
    [RelatedId]  ,
    [AuditTargetTypeId])
SELECT
TOP(25650)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1, 
('1DD87CF1-286B-409A-8C60-3FFEC394FDB1') , 
CASE WHEN ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 510 = 30 then 29 ELSE ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 510 END as rownum2 -- TILL 50 and no 30
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2

-- Extra matches with 30
SELECT MAX([Id]) FROM [dbo].[Audits];
--28000001 Upper value

INSERT INTO  [dbo].[Audits] WITH(TABLOCK) 
([TargetTypeId],
    [TargetId],
    [TargetName],
    [Action],
    [ActionOverride] ,
    [Date] ,
    [UserDisplayName],
    [DescriptionData],
    [IsNotification]) 
SELECT top(241829) 30 as rownum2 -- TILL 50 and no 30
,'bla','bla2',1,1,getdate(),'bla3','Bla4',1
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2;



;WITH CTE AS
(SELECT
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1, 
('1DD87CF1-286B-409A-8C60-3FFEC394FDB1') as gu , 
30 as rownum2 -- TILL 50 and no 30
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2
CROSS APPLY master.dbo.spt_values spt3
)
--267479 - 25650 = 241829
INSERT INTO  [dbo].[AuditRelatedIds] WITH(TABLOCK)
    ([AuditId] ,
    [RelatedId]  ,
    [AuditTargetTypeId])

SELECT TOP(241829) rownum1,gu,rownum2 FROM CTE
WHERE rownum1 > 28000001
ORDER BY rownum1 ASC;
Randi Vertongen
sumber
Penjelasan yang sangat bagus! Menambahkan MAXDOP 0tampaknya telah memperbaikinya. Terima kasih banyak!
Chocoman
1
MAXDOP 1 ** (salah ketik)
Chocoman
@Choman Bagus! Senang membantu :)
Randi Vertongen
1

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.IDterkait dengan ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'lalu filter daftar itu ke orang-orang yang Audit.TargetTypeIDada dalam daftar.

Dengan versi kedua filter utama berasal yang Audit.IDterkait dengan daftar Audit.TargetTypeID.

Karena penambahan Audit.TargetTypeID = 30tampaknya 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 oleh AuditRelatedID.RelatedIDuntuk kemudian menemukan Audit.IDmungkin lebih selektif daripada mencoba menggunakan Audit.TargetTypeIDuntuk kemudian menemukan Audit.ID.

Untuk kredit ypercube. Anda tentu saja dapat memperbarui [AuditRelatedIds].[IX_AuditRelatedIdsRelatedId_INCLUDES]untuk memiliki keduanya RelatedIDdan AuditIDsebagai bagian dari indeks bukannya AuditIDsebagai bagian dari INCLUDE. Seharusnya tidak mengambil ruang indeks tambahan dan akan memungkinkan Anda untuk menggunakan kedua kolom dalam JOINklausa. Itu dapat membantu Pengoptimal Kueri membuat rencana eksekusi yang sama untuk kedua kueri.

Beroperasi dengan logika yang serupa, mungkin ada beberapa manfaat untuk indeks Audityang berisi TargetTypeID ASC, ID ASCpada node dipesan / filter aktual (bukan sebagai bagian dari INCLUDE). Ini harus memungkinkan pengoptimal Kueri untuk memfilter Audit.TargetTypeIDsaat itu dengan cepat bergabung AuditReferenceIds.AuditID. Sekarang ini mungkin berakhir dengan kedua pertanyaan memilih paket yang kurang efisien jadi saya hanya akan mencobanya setelah mencoba rekomendasi ypercube.

Kirk Saunders
sumber