我遇到了一个问题,当我对视图运行查询时,它需要 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
Run Code Online (Sandbox Code Playgroud)
添加 Partition by 并获得与临时查询相似的结果。
Ran*_*gen 19
这里的主要区别在于,性能更好的查询是CodeMasterID在所有 4 个表(2 个临时表(实际和历史))上下推搜索谓词,其中视图上的选择似乎直到结束(过滤器运算符)才这样做。
特尔博士;
问题是由于在某些情况下(例如视图)参数没有下推到窗口函数。最简单的解决方案是添加OPTION(RECOMPILE)到视图调用中,以便优化器在运行时“看到”参数(如果可能的话)。如果为每个查询调用重新编译执行计划的成本太高,使用需要参数的内联表值函数可能是一种解决方案。有一位优秀的博文由保罗·怀特这一点。有关查找和解决特定问题的更详细方法,请继续阅读。
性能更好的查询
代码管理员表
交易表
我喜欢早上搜索谓词的味道
大坏查询
代码管理员表
这是一个仅限谓词的区域
交易表
但是优化器没有读取“交易的艺术™”
...并且不吸取过去的教训
直到所有数据到达过滤器运算符
这里的主要问题是优化器由于视图中的窗口函数而无法在运行时“看到”参数,并且无法使用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;
Run Code Online (Sandbox Code Playgroud)
导致大约 4.5 秒的 CPU 时间和 3.2 秒的经过时间
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
Run Code Online (Sandbox Code Playgroud)
当我们加入甜蜜的拥抱 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;
Run Code Online (Sandbox Code Playgroud)
这一切都很好。
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
Run Code Online (Sandbox Code Playgroud)
为什么
这一切再次支持无法将@P1谓词应用于表的观点,因为窗口函数和参数化导致过滤器运算符
不仅是临时表的问题
见附录 2
编写这样的查询时会看到相同的结果:
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
Run Code Online (Sandbox Code Playgroud)
同样,优化器在应用窗口函数之前不会下推谓词。
省略 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;
Run Code Online (Sandbox Code Playgroud)
一切都很好
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.
Run Code Online (Sandbox Code Playgroud)
那么这一切让我们何去何从呢?
本ROW_NUMBER()就不好了查询应用过滤器之前的计算方法。
所有这些都让我们看到了Paul White 于2013 年撰写的关于窗口函数和视图的博文。
我们示例的重要部分之一是以下语句:
不幸的是,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;
Run Code Online (Sandbox Code Playgroud)
这部分对应于我们在自己声明参数/SP_EXECUTESQL在视图上使用时所看到的。
1:选项(重新编译)
我们知道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
Run Code Online (Sandbox Code Playgroud)
导致预期的搜索谓词
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
Run Code Online (Sandbox Code Playgroud)
在我的测试中大约有 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;
Run Code Online (Sandbox Code Playgroud)
应该有其他创造性的方法来解决这个问题,重要的部分是知道是什么原因造成的。
附录#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
Run Code Online (Sandbox Code Playgroud)
附录#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
Run Code Online (Sandbox Code Playgroud)