Sự khác biệt hiệu suất chính
Sự khác biệt chính ở đây là truy vấn hoạt động tốt hơn đang đẩy xuống vị từ tìm kiếm CodeMasterID
trên tất cả 4 bảng (2 bảng tạm thời (thực tế & lịch sử)) trong đó lựa chọn trên chế độ xem dường như không làm điều đó cho đến khi kết thúc (toán tử bộ lọc) .
TL DR;
Vấn đề là do các tham số không được đẩy xuống các chức năng của cửa sổ trong một số trường hợp nhất định như chế độ xem. Giải pháp đơn giản nhất là thêm OPTION(RECOMPILE)
vào lệnh gọi xem để làm cho trình tối ưu hóa 'xem' các thông số trong thời gian chạy nếu đó là một khả năng. Nếu quá tốn kém để biên dịch lại kế hoạch thực hiện cho mỗi cuộc gọi truy vấn, sử dụng hàm có giá trị bảng nội tuyến dự kiến tham số có thể là một giải pháp. Có một Blogpost tuyệt vời của Paul White về điều này. Để biết cách chi tiết hơn về việc tìm kiếm và giải quyết vấn đề cụ thể của bạn, hãy tiếp tục đọc.
Truy vấn thực hiện tốt hơn
Bảng Codemaster


Bảng thỏa thuận


Tôi yêu mùi tìm kiếm vị ngữ vào buổi sáng
Truy vấn xấu lớn
Bảng Codemaster


Đây là một khu vực chỉ vị ngữ
Bảng giao dịch

Nhưng trình tối ưu hóa đã không đọc art Nghệ thuật của thỏa thuận ™ "

... và không học hỏi từ quá khứ
Cho đến khi tất cả dữ liệu đó đến được toán tử lọc

Vì vậy, những gì cho?
Vấn đề chính ở đây là trình tối ưu hóa không 'nhìn thấy' các tham số khi chạy do các chức năng của cửa sổ trong chế độ xem và không thể sử dụng SelOnSeqPrj
(chọn dự án trình tự, tiếp tục trong bài này để tham khảo) .
Tôi đã có thể sao chép các kết quả tương tự với một mẫu thử nghiệm và sử dụng SP_EXECUTESQL
để tham số hóa cuộc gọi đến chế độ xem. Xem phụ lục cho DDL / DML
thực hiện một truy vấn đối với chế độ xem thử nghiệm với chức năng cửa sổ và INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Kết quả là khoảng 4,5 giây thời gian cpu và thời gian trôi qua 3,2 giây
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
Khi chúng ta thêm cái ôm ngọt ngào của 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;
Tất cả đều tốt.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Tại sao
Tất cả điều này một lần nữa hỗ trợ điểm không thể áp dụng biến @P1
vị ngữ cho các bảng do chức năng cửa sổ & tham số hóa dẫn đến toán tử bộ lọc

Không chỉ là một vấn đề cho các bảng thời gian
Xem phụ lục 2
Ngay cả khi không sử dụng bảng tạm thời, điều này vẫn xảy ra:

Kết quả tương tự được nhìn thấy khi viết truy vấn như thế này:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Một lần nữa, trình tối ưu hóa không đẩy xuống vị ngữ trước khi áp dụng chức năng cửa sổ.
Khi bỏ qua 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;
Tất cả đều tốt
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.
Vậy tất cả những gì rời bỏ chúng ta?
Các ROW_NUMBER()
được tính toán trước khi bộ lọc được áp dụng trên các truy vấn xấu.
Và tất cả điều này dẫn chúng ta đến blogpost này từ năm 2013 bởi Paul White
về các chức năng và khung nhìn của cửa sổ.
Một trong những phần quan trọng cho ví dụ của chúng tôi là tuyên bố này:
Thật không may, quy tắc đơn giản hóa SelOnSeqPrj chỉ hoạt động khi vị từ thực hiện so sánh với hằng số. Vì lý do đó, truy vấn sau đây tạo ra gói tối ưu phụ trên SQL Server 2008 trở lên:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;

Phần này tương ứng với những gì chúng ta đã thấy khi tự khai báo tham số / sử dụng SP_EXECUTESQL
trên khung nhìn.
Các giải pháp thực tế
1: TÙY CHỌN (KHUYẾN NGHỊ)
Chúng tôi biết rằng OPTION(RECOMPILE)
để 'thấy' giá trị trong thời gian chạy là một khả năng. Khi biên dịch lại kế hoạch thực hiện cho mỗi cuộc gọi truy vấn quá tốn kém, có các giải pháp khác.
2: Hàm nội tuyến có giá trị với một tham số
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
Kết quả trong các vị từ tìm kiếm dự kiến
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
Với khoảng 9 lần đọc logic trong bài kiểm tra của tôi
3: Viết truy vấn mà không sử dụng chế độ xem.
'Giải pháp' khác có thể viết hoàn toàn truy vấn mà không cần sử dụng chế độ xem.
4: Không giữ ROW_NUMBER()
chức năng trong chế độ xem, thay vào đó chỉ định chức năng đó trong cuộc gọi đến chế độ xem.
Một ví dụ về điều này sẽ là:
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;
Cần có những cách sáng tạo khác xung quanh vấn đề này, phần quan trọng là biết nguyên nhân gây ra nó.
Phụ lục số 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
Phụ lục số 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