Pilih * dari Tampilan membutuhkan waktu 4 menit

11

Saya menjalankan masalah di mana ketika saya menjalankan kueri terhadap tampilan dibutuhkan 4+ menit. Namun, ketika saya menjalankan nyali kueri itu selesai dalam 1 detik.

Satu-satunya hal yang saya tidak yakin tentang tabel yang bergabung adalah tabel temporal.

Paket permintaan ad hoc: https://www.brentozar.com/pastetheplan/?id=BykohB2p4

Lihat paket permintaan: https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E

Ada saran tentang di mana mencoba dan mencari tahu ini?

Lihat kode:

ALTER VIEW [dbo].[vwDealHistoryPITA]
AS
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.LastUpdateDate) AS Deal_HistoryID,
       cm.CodeMasterID,
       cm.ProjectName,
       cm.[Status],
       d.CompanyID,
       d.DealTypeMasterID,
       cm.[Description],
       d.PassiveInd,
       d.ApproxTPGOwnership,
       d.NumberBoardSeats,
       d.FollowonInvestmentInd,
       d.SocialImpactInd,
       d.EquityInd,
       d.DebtInd,
       d.RealEstateInd,
       d.TargetPctgReturn,
       d.ApproxTotalDealSize,
       cm.CurrencyCode,
       d.ConflictCheck,
       cm.CreatedDate,
       cm.CreatedBy,
       cm.LastUpdateDate,
       cm.LastUpdateBy,
       d.ExpensesExceedThresholdDate,
       d.CurrentTPGCheckSize,
       d.PreferredEquityInd,
       d.ConvertibleDebtInd,
       d.OtherRealAssetsInd,
       d.InitialTPGCheckSize,
       d.DirectLendingInd,
       cm.NameApproved,
       cm.FolderID,
       cm.CodaProcessedDateTime,
       cm.DeadDate,
       d.SectorMasterID,
       d.DTODataCompleteDate,
       cm.ValidFrom AS CodeMasterValidFrom,
       cm.ValidTo   AS CodeMasterValidTo,
       d.validFrom  AS DealValidFrom,
       d.validTo    AS DealValidTo
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm 
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID;
GO

Menambahkan Partisi dengan dan mendapatkan hasil yang mirip dengan permintaan ad hoc.

pengguna761786
sumber

Jawaban:

18

Perbedaan kinerja utama

Perbedaan utama di sini adalah bahwa kueri yang berkinerja lebih baik menekan predikat pencarian CodeMasterIDpada semua 4 tabel (2 tabel temporal (aktual & riwayat)) di mana pilih pada tampilan tampaknya tidak melakukan itu sampai akhir (operator filter) .

TL DR;

Masalahnya adalah karena parameter tidak mendorong ke fungsi jendela dalam kasus-kasus tertentu seperti tampilan. Solusi termudah adalah menambahkan OPTION(RECOMPILE)panggilan tampilan untuk membuat pengoptimal 'melihat' params saat runtime jika itu suatu kemungkinan. Jika terlalu mahal untuk mengkompilasi ulang rencana eksekusi untuk setiap panggilan kueri, menggunakan fungsi tabel inline bernilai yang mengharapkan parameter bisa menjadi solusi. Ada Blogpost yang bagus dari Paul White tentang ini. Untuk cara yang lebih terperinci dalam menemukan dan menyelesaikan masalah khusus Anda, teruslah membaca.


Kueri yang berkinerja lebih baik

Tabel codemaster

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Tabel kesepakatan

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Saya suka aroma mencari predikat di pagi hari


Permintaan buruk besar

Tabel codemaster

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Ini hanya zona predikat

Tabel Kesepakatan

masukkan deskripsi gambar di sini

Tetapi pengoptimal tidak membaca 'The art of the deal ™ "

masukkan deskripsi gambar di sini

... dan tidak belajar dari masa lalu

Hingga semua data mencapai operator filter

masukkan deskripsi gambar di sini


Jadi, apa yang menyebabkannya?

Masalah utama di sini adalah optimizer tidak 'melihat' parameter saat runtime karena fungsi jendela dalam tampilan dan tidak dapat menggunakan SelOnSeqPrj (pilih proyek urutan, lebih jauh ke bawah dalam posting ini untuk referensi) .

Saya bisa meniru hasil yang sama dengan sampel uji dan menggunakan SP_EXECUTESQLuntuk parameterisasi panggilan ke tampilan. Lihat tambahan untuk DDL / DML

mengeksekusi kueri terhadap tampilan uji dengan fungsi jendela dan INNER JOIN

SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;

Menghasilkan sekitar 4,5 detik waktu cpu dan 3,2 detik berlalu

 SQL Server Execution Times:
   CPU time = 4595 ms,  elapsed time = 3209 ms.

Ketika kami menambahkan pelukan manis OPTION(RECOMPILE)

SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155; 

Semuanya baik.

 SQL Server Execution Times:
   CPU time = 16 ms,  elapsed time = 98 ms.

Mengapa

Ini semua lagi mendukung titik tidak dapat menerapkan @P1predikat ke tabel karena fungsi jendela & parameterisasi yang mengakibatkan operator filter

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Bukan hanya masalah untuk tabel temporal

Lihat lampiran 2

Bahkan ketika tidak menggunakan tabel temporal, ini terjadi: masukkan deskripsi gambar di sini

Hasil yang sama terlihat saat menulis kueri seperti ini:

DECLARE @P1 int = 37155
SELECT * FROM  dbo.Bad2
Where CodeMasterID = @P1;

Sekali lagi, pengoptimal tidak menekan predikat sebelum menerapkan fungsi jendela.

Saat menghilangkan ROW_NUMBER ()

CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2  cm 
INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID;

Semuanya baik-baik saja

SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155


 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 33 ms.

jadi di mana semua itu meninggalkan kita?

Itu ROW_NUMBER()dihitung sebelum filter diterapkan pada kueri buruk.

Dan semua ini membawa kita ke posting blog ini dari 2013 oleh Paul White pada fungsi dan tampilan jendela.

Salah satu bagian penting untuk contoh kita adalah pernyataan ini:

Sayangnya, aturan penyederhanaan SelOnSeqPrj hanya berfungsi ketika predikat melakukan perbandingan dengan konstanta. Untuk alasan itu, kueri berikut menghasilkan rencana sub-optimal pada SQL Server 2008 dan yang lebih baru:

DECLARE @ProductID INT = 878;

SELECT
    mrt.ProductID,
    mrt.TransactionID,
    mrt.ReferenceOrderID,
    mrt.TransactionDate,
    mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt 
WHERE
    mrt.ProductID = @ProductID;

masukkan deskripsi gambar di sini

Bagian ini sesuai dengan apa yang telah kita lihat ketika mendeklarasikan parameter sendiri / menggunakan SP_EXECUTESQLpada tampilan.


Solusi aktual

1: OPTION (RECOMPILE)

Kita tahu bahwa OPTION(RECOMPILE)'melihat' nilai saat runtime adalah suatu kemungkinan. Saat mengkompilasi ulang rencana eksekusi untuk setiap panggilan permintaan terlalu mahal, ada solusi lain.

2: Inline table dihargai fungsi dengan suatu parameter

CREATE FUNCTION dbo.BlaBla
(
    @P1 INT
)  
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
    (
     SELECT 
     ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
     cm.CodeMasterID,CM.ManagerID,
     cm.ParentDeptID,d.DealID,
     d.CodeMasterID as dealcodemaster,
     d.EvenMoreBlaID
    FROM dbo.CodeMaster2  cm 
    INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID
    Where cm.CodeMasterID = @P1
    ) 
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155

Menghasilkan predikat pencarian yang diharapkan

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 0 ms.

Dengan sekitar 9 pembacaan logis pada tes saya

3: Menulis kueri tanpa menggunakan tampilan.

'Solusi' lain bisa menulis kueri sepenuhnya tanpa menggunakan tampilan.

4: Tidak menjaga ROW_NUMBER()fungsi dalam tampilan, melainkan menetapkannya dalam panggilan ke tampilan.

Contohnya adalah:

CREATE VIEW dbo.Bad2
as
SELECT 
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2  cm 
INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID;

GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM  dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;

Seharusnya ada cara kreatif lain untuk mengatasi masalah ini, yang terpenting adalah mengetahui apa yang menyebabkannya.


Adendum # 1

CREATE TABLE dbo.Codemaster   
(    
     CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED  
   , ManagerID INT  NULL  
   , ParentDeptID int NULL  
   , SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL  
   , SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL  
   , PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)     
)    
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))   
;  

CREATE TABLE dbo.Deal   
(    
     DealID int NOT NULL PRIMARY KEY CLUSTERED  
   , CodeMasterID INT  NULL  
   , EvenMoreBlaID int NULL  
   , SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL  
   , SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL  
   , PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)     
)    
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))   
;  

INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;


INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;

CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);

CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);


SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.* 
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm 
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;

-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm 
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID

GO
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155

-- Very bad shame on you

Tambahan # 2

CREATE TABLE dbo.Codemaster2
(    
     CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED  
   , ManagerID INT  NULL  
   , ParentDeptID int NULL  

);  

CREATE TABLE dbo.Deal2
(    
     DealID int NOT NULL PRIMARY KEY CLUSTERED  
   , CodeMasterID INT  NULL  
   , EvenMoreBlaID int NULL    
);  

INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;


INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;

CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);


SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.* 
FROM dbo.CodeMaster2 cm 
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;

-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2  cm 
INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID

GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
Randi Vertongen
sumber