ความแตกต่างของประสิทธิภาพหลัก
ความแตกต่างหลักที่นี่เป็นที่แบบสอบถามประสิทธิภาพดีจะผลักดันลงแสวงหาคำกริยาใน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