由于我的性能调优技能似乎永远不够用,我总是想知道是否可以针对某些查询执行更多优化。这个问题涉及的情况是嵌套在子查询中的 Windowed MAX 函数。
我正在挖掘的数据是各种大型集合组上的一系列事务。我有 4 个重要字段,交易的唯一 ID,一批交易的组 ID,以及与相应的唯一交易或交易组相关的日期。大多数情况下,组日期与批次的最大唯一交易日期匹配,但有时会通过我们的系统进行手动调整,并且在捕获组交易日期后会发生唯一日期操作。此手动编辑不会按设计调整组日期。
我在此查询中确定的是唯一日期在组日期之后的那些记录。以下示例查询构建了我的场景的大致等效项,并且 SELECT 语句返回我正在寻找的记录,但是,我是否以最有效的方式接近此解决方案?在我的事实表加载过程中,这需要一段时间才能运行,因为我的记录计数高 9 位数字,但主要是我对子查询的蔑视让我想知道这里是否有更好的方法。我并不担心任何索引,因为我相信这些索引已经到位;我正在寻找的是一种替代查询方法,它可以实现相同的目标,但效率更高。欢迎任何反馈。
CREATE TABLE #Example
(
UniqueID INT IDENTITY(1,1)
, GroupID INT
, GroupDate DATETIME
, UniqueDate DATETIME
)
CREATE CLUSTERED INDEX [CX_1] ON [#Example]
(
[UniqueID] ASC
)
SET NOCOUNT ON
--Populate some test data
DECLARE @i INT = 0, @j INT = 5, @UniqueDate DATETIME, @GroupDate DATETIME
WHILE @i < 10000
BEGIN
IF((@i + @j)%173 = 0)
BEGIN
SET @UniqueDate = GETDATE()+@i+5
END
ELSE
BEGIN
SET @UniqueDate = GETDATE()+@i
END
SET @GroupDate = GETDATE()+(@j-1)
INSERT INTO #Example (GroupID, GroupDate, UniqueDate)
VALUES (@j, @GroupDate, @UniqueDate)
SET @i = @i + 1
IF (@i % 5 = 0)
BEGIN
SET @j = @j+5
END
END
SET NOCOUNT OFF
CREATE NONCLUSTERED INDEX [IX_2_4_3] ON [#Example]
(
[GroupID] ASC,
[UniqueDate] ASC,
[GroupDate] ASC
)
INCLUDE ([UniqueID])
-- Identify any UniqueDates that are greater than the GroupDate within their GroupID
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
FROM (
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
, MAX(UniqueDate) OVER (PARTITION BY GroupID) AS maxUniqueDate
FROM #Example
) calc_maxUD
WHERE maxUniqueDate > GroupDate
AND maxUniqueDate = UniqueDate
DROP TABLE #Example
dbfiddle在这里
当您能够从 SQL Server 2012 升级到 SQL Server 2016 时,您可能能够利用新的批处理模式 Window Aggregate 运算符提供的性能大大提高(尤其是对于无框架窗口聚合)。
几乎所有的大型数据处理方案都使用列存储存储比使用行存储更好。即使不为您的基表更改为列存储,您仍然可以通过在其中一个基表上创建一个空的非聚集列存储过滤索引或通过冗余外部连接到一个列存储组织来获得新的 2016 运算符和批处理模式执行的好处桌子。
使用第二个选项,查询变为:
db<>小提琴
请注意,对原始查询的唯一更改是创建一个空的临时表并添加左连接。执行计划是:
有关更多信息和选项,请参阅 Itzik Ben-Gan 的优秀系列,关于 SQL Server 2016 中的批处理模式窗口聚合运算符您需要了解的内容(分三部分)。
我假设没有索引,因为您没有提供任何索引。
马上,以下索引将消除计划中的排序运算符,否则可能会消耗大量内存:
在这种情况下,子查询不是性能问题。如果有的话,我会考虑消除窗口函数(MAX...OVER)以避免嵌套循环和表假脱机构造的方法。
使用相同的索引,下面的查询乍一看可能效率较低,它确实对基表进行了 2 到 3 次扫描,但由于缺少 Spool 操作符,它在内部消除了大量读取。我猜它的性能仍然会更好,特别是如果您的服务器上有足够的 CPU 内核和 IO 性能:
(注意:我添加了一个
MERGE JOIN
查询提示,但如果您的统计信息有序,这可能会自动发生。最佳做法是尽可能留下这些提示。)我只是要把 ol' Cross Apply 扔出去:
有一些有点什么索引,它做得很好。
统计时间和 io 看起来像这样(您的查询是第一个结果)
查询计划在这里(同样,您的计划是第一个):
https://www.brentozar.com/pastetheplan/?id=BJYJvqAal
为什么我更喜欢这个版本?我避开线轴。如果这些开始溢出到磁盘,它会变得丑陋。
但你可能也想试试这个。
如果这是一个大型 DW,您可能更喜欢 Hash Join,并且在 join 中进行行过滤,而不是在
TOP 1
查询末尾作为 Filter 运算符。计划在这里:https ://www.brentozar.com/pastetheplan/?id=BkUF55ATx
在这里统计时间和 io:
希望这可以帮助!
一次编辑,基于@ypercube 的想法和一个新索引。
这是统计时间和 io:
这是计划:
https://www.brentozar.com/pastetheplan/?id=SJv8foR6g
我会看看
top with ties
如果
GroupDate
是相同的GroupId
那么:其他:
top with ties
在公用表表达式中使用dbfiddle:http ://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=c058994c2f5f3d99b212f06e1dae9fd3
原始查询
vs
top with ties
在公用表表达式中因此,我对迄今为止发布的各种方法进行了一些分析,在我的环境中,看起来 Daniel 的方法在执行时间上始终胜出。令人惊讶的是(对我来说)sp_BlitzErik 的第三个 CROSS APPLY 方法并没有落后太多。如果有人感兴趣,这是输出,但感谢 TON 提供的所有替代方法。我从挖掘这个问题的答案中学到的东西比我在很长一段时间内学到的更多!