เลือก * จากมุมมองใช้เวลา 4 นาที


11

ฉันกำลังประสบปัญหาซึ่งเมื่อฉันเรียกใช้แบบสอบถามกับมุมมองจะใช้เวลา 4+ นาที อย่างไรก็ตามเมื่อฉันเรียกใช้ความกล้าของแบบสอบถามมันจะเสร็จสิ้นในเวลา 1 วินาที

สิ่งเดียวที่ฉันไม่แน่ใจเกี่ยวกับคือตารางที่เข้าร่วมเป็นตารางชั่วคราว

แผนคิวรีแบบเฉพาะกิจ: https://www.brentozar.com/pastetheplan/?id=BykohB2p4

ดูแผนแบบสอบถาม: https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E

มีข้อเสนอแนะเกี่ยวกับสถานที่ที่จะลองและคิดออก

ดูรหัส:

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

เพิ่มพาร์ติชันด้วยและรับผลลัพธ์ที่คล้ายกันในคิวรีเฉพาะกิจ

คำตอบ:


18

ความแตกต่างของประสิทธิภาพหลัก

ความแตกต่างหลักที่นี่เป็นที่แบบสอบถามประสิทธิภาพดีจะผลักดันลงแสวงหาคำกริยาในCodeMasterIDทุกตาราง 4 (2 ตารางชั่วคราว (ที่เกิดขึ้นจริงและประวัติศาสตร์)) ที่เลือกจะปรากฏขึ้นในมุมมองที่จะไม่ทำอย่างนั้นจนกว่าจะสิ้นสุด(ผู้ประกอบการกรอง)

TL DR;

ปัญหานี้เกิดจากพารามิเตอร์ที่ไม่ได้กดลงไปที่ฟังก์ชันหน้าต่างในบางกรณีเช่นมุมมอง ทางออกที่ง่ายที่สุดคือการเพิ่มOPTION(RECOMPILE)ในการเรียกดูเพื่อให้เครื่องมือเพิ่มประสิทธิภาพ 'ดู' params ที่รันไทม์หากเป็นไปได้ ถ้ามันแพงเกินไปที่จะคอมไพล์แผนปฏิบัติการสำหรับการเรียกคิวรีแต่ละครั้งการใช้ฟังก์ชันที่มีค่าในตารางแบบอินไลน์ที่คาดว่าพารามิเตอร์อาจเป็นโซลูชัน มีBlogpost ที่ยอดเยี่ยมโดยPaul Whiteในเรื่องนี้ สำหรับรายละเอียดเพิ่มเติมในการค้นหาและแก้ไขปัญหาเฉพาะของคุณโปรดอ่านต่อไป


แบบสอบถามที่มีประสิทธิภาพดีกว่า

ตาราง Codemaster

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ตารางดีล

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ฉันชอบกลิ่นของเพรดิเคตในตอนเช้า


แบบสอบถามที่ไม่ดีขนาดใหญ่

ตาราง Codemaster

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

นี่เป็นเพรดิเคตโซนเท่านั้น

ตารางดีล

ป้อนคำอธิบายรูปภาพที่นี่

แต่เครื่องมือเพิ่มประสิทธิภาพไม่ได้อ่าน 'ศิลปะของการจัดการ™ "

ป้อนคำอธิบายรูปภาพที่นี่

... และไม่ได้เรียนรู้จากที่ผ่านมา

จนกว่าข้อมูลทั้งหมดนั้นจะไปถึงผู้ดำเนินการตัวกรอง

ป้อนคำอธิบายรูปภาพที่นี่


แล้วอะไรล่ะ

ปัญหาหลักที่นี่คือการเพิ่มประสิทธิภาพไม่ได้ 'เห็น' พารามิเตอร์ที่รันไทม์เนื่องจากฟังก์ชั่นหน้าต่างในมุมมองและไม่สามารถที่จะใช้(เลือกในโครงการลำดับลดลงต่อไปในโพสต์นี้สำหรับการอ้างอิง)SelOnSeqPrj

ฉันสามารถทำซ้ำผลลัพธ์เดียวกันด้วยตัวอย่างทดสอบและใช้SP_EXECUTESQLเพื่อกำหนดพารามิเตอร์การเรียกไปยังมุมมอง ดูภาคผนวกสำหรับ DDL / DML

ดำเนินการแบบสอบถามกับมุมมองทดสอบด้วยฟังก์ชั่นหน้าต่างและ INNER JOIN

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

ส่งผลให้ประมาณ 4.5 วินาทีของเวลา cpu และเวลาที่ผ่านไป 3.2 วินาที

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

เมื่อเราเพิ่มการโอบกอดหวานของ 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; 

มันเป็นสิ่งที่ดีทั้งหมด

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

ทำไม

ทั้งหมดนี้สนับสนุนจุดที่ไม่สามารถใช้เพรดิเคต@P1กับตารางอีกครั้งเนื่องจากฟังก์ชั่นหน้าต่าง & การกำหนดพารามิเตอร์ส่งผลให้ตัวดำเนินการตัวกรอง

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

ไม่เพียง แต่ปัญหาสำหรับตารางชั่วคราวเท่านั้น

ดูภาคผนวก 2

แม้ว่าจะไม่ได้ใช้ตารางชั่วคราว แต่สิ่งนี้ก็เกิดขึ้น: ป้อนคำอธิบายรูปภาพที่นี่

ผลลัพธ์เดียวกันจะเห็นเมื่อเขียนแบบสอบถามเช่นนี้:

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

อีกครั้งเครื่องมือเพิ่มประสิทธิภาพจะไม่กดลงภาคก่อนที่จะใช้ฟังก์ชั่นหน้าต่าง

เมื่อข้าม 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;

ทั้งหมดเป็นอย่างดี

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.

แล้วอะไรที่ทำให้เราหาย

ROW_NUMBER()มีการคำนวณก่อนที่จะกรองจะถูกนำมาใช้ในการค้นหาที่ไม่ดี

และทั้งหมดนี้นำเราไปสู่บล็อกนี้จาก 2013 โดยPaul White ในฟังก์ชั่นหน้าต่างและมุมมอง

หนึ่งในส่วนที่สำคัญสำหรับตัวอย่างของเราคือคำสั่งนี้:

น่าเสียดายที่กฎการทำให้เข้าใจง่ายของ SelOnSeqPrj จะทำงานได้เมื่อเพรดิเคตทำการเปรียบเทียบกับค่าคงที่เท่านั้น ด้วยเหตุนี้แบบสอบถามต่อไปนี้จึงสร้างแผนย่อยที่เหมาะสมที่สุดบน SQL Server 2008 และใหม่กว่า:

DECLARE @ProductID INT = 878;

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

ป้อนคำอธิบายรูปภาพที่นี่

ส่วนนี้สอดคล้องกับสิ่งที่เราได้เห็นเมื่อประกาศพารามิเตอร์ตัวเอง / ใช้SP_EXECUTESQLในมุมมอง


ทางออกที่แท้จริง

1: ตัวเลือก (RECOMPILE)

เรารู้ว่าOPTION(RECOMPILE)การ 'เห็น' คุณค่าของรันไทม์เป็นไปได้ เมื่อรวบรวมแผนการดำเนินการซ้ำสำหรับการเรียกแบบสอบถามแต่ละครั้งมีราคาแพงเกินไปมีวิธีแก้ไขปัญหาอื่น ๆ

2: ฟังก์ชันที่มีค่าในตารางแบบอินไลน์พร้อมพารามิเตอร์

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

ส่งผลให้ภาคแสดงการแสวงหาที่คาดหวัง

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

ด้วยตรรกะประมาณ 9 อ่านในการทดสอบของฉัน

3: การเขียนแบบสอบถามโดยไม่ต้องใช้มุมมอง

'การแก้ปัญหา' อื่น ๆ สามารถเขียนแบบสอบถามทั้งหมดโดยไม่ต้องใช้มุมมอง

4: ไม่เก็บROW_NUMBER()ฟังก์ชันในมุมมองแทนการระบุในการเรียกไปยังมุมมองแทน

ตัวอย่างของสิ่งนี้จะเป็น:

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;

ควรมีวิธีที่สร้างสรรค์อื่น ๆ เกี่ยวกับปัญหานี้ส่วนสำคัญคือการรู้ว่าอะไรเป็นสาเหตุ


ภาคผนวก # 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

ภาคผนวก # 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.