Ana performans farklılıkları
Buradaki ana farklar, daha iyi performans gösteren sorgunun, CodeMasterIDgörünümdeki seçimin sonuna kadar (filtre operatörü) bunu yapmadığı göründüğü 4 tablodaki (2 geçici tablo (gerçek ve geçmiş)) arama yüklemesini aşağı itmesidir .
TL DR;
Sorun, görünümler gibi bazı durumlarda pencere işlevlerine basmayan parametrelerden kaynaklanmaktadır. En kolay çözüm OPTION(RECOMPILE), bir olasılık varsa, optimize edicinin çalışma zamanında parametreleri görmesini sağlamak için görüntüleme çağrısına eklemektir . Her sorgu çağrısı için yürütme planını yeniden derlemek çok pahalıysa, bir parametre bekleyen satır içi tablo değerli bir işlev kullanmak bir çözüm olabilir. Mükemmel bir var blogpost tarafından Paul Beyaz bu konuda. Sorununuzu bulma ve çözme konusunda daha ayrıntılı bir yol için okumaya devam edin.
Daha iyi performans gösteren sorgu
Codemaster tablosu


Anlaşma tablosu


Sabahları arama tahminlerinin kokusunu seviyorum
Büyük Kötü Sorgu
Codemaster tablosu


Bu tek yüklem bölgesi
Anlaşma tablosu

Ancak optimize edici 'Anlaşma sanatı ™'

... ve geçmişten ders almıyor
Tüm bu veriler filtre operatörüne ulaşıncaya kadar

Peki, ne veriyor?
Buradaki ana sorun, optimize edicinin, görünümdeki pencere işlevleri nedeniyle çalışma zamanında parametreleri 'görmemesi' ve kullanamamasıdır SelOnSeqPrj (sıra projesinde seçin, referans için bu yazıda daha aşağıda) .
Aynı sonuçları bir test örneği ile çoğaltmayı SP_EXECUTESQLve görünüme yapılan çağrıyı parametreleştirmeyi kullanarak başardım . DDL / DML için eke bakın
pencere fonksiyonuna sahip bir test görünümüne karşı bir sorgu yürütme ve INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
Sonuç olarak 4.5s cpu süresi ve 3.2s geçen süre
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
Tatlı kucağını eklediğimizde 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;
Her şey yolunda.
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Neden
Bu @P1, filtre operatörü ile sonuçlanan pencere fonksiyonu ve parametreleştirmesi nedeniyle tabloları yüklemeyi uygulayamama noktasını tekrar desteklemektedir.

Yalnızca geçici tablolar için bir sorun değil
Zeyilname 2'ye bakınız
Geçici tablolar kullanılmasa bile, bu olur:

Sorgu şöyle yazılırken aynı sonuç görülür:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Yine, optimize edici pencere işlevini uygulamadan önce yüklemi aşağıya itmiyor.
ROW_NUMBER () parametresini atlarken
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;
Herşey iyi
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.
Peki bütün bunlar bizi nereye bırakıyor?
ROW_NUMBER()Filtre kötü sorgulara uygulanmadan önce hesaplanır.
Ve tüm bunlar bizi Paul White
tarafından pencere fonksiyonları ve görünümleri üzerine 2013'ten bu blog yayınına götürüyor .
Örneğimiz için önemli kısımlardan biri bu ifadedir:
Ne yazık ki, SelOnSeqPrj sadeleştirme kuralı yalnızca yüklem, bir sabitle karşılaştırma yaptığında çalışır. Bu nedenle, aşağıdaki sorgu SQL Server 2008 ve sonraki sürümlerde en uygun olmayan planı üretir:
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;

Bu bölüm, parametreyi kendimiz beyan ederken / SP_EXECUTESQLgörünümde kullanırken gördüklerimize karşılık gelir .
Gerçek çözümler
1: SEÇENEK (TAVSİYE)
Çalışma OPTION(RECOMPILE)zamanında değeri 'görmek' için bir olasılık olduğunu biliyoruz . Her sorgu çağrısı için yürütme planını yeniden derlemek çok pahalı olduğunda, başka çözümler de vardır.
2: Bir parametre ile satır içi tablo değerli işlev
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
Beklenen arama sonuçlarının ortaya çıkması
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
Testimde yaklaşık 9 mantıksal okuma ile
3: Bir görünüm kullanmadan sorguyu yazma.
Diğer 'çözüm', bir görünüm kullanmadan sorguyu tamamen yazmak olabilir.
4: ROW_NUMBER()İşlevi görünümde tutmamak, bunun yerine görünüm çağrısında belirtmek.
Buna bir örnek:
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;
Bu konuyla ilgili başka yaratıcı yollar olmalı, önemli olan buna neyin sebep olduğunu bilmektir.
Zeyil # 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
Zeyilname # 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