ROW_NUMBER()OVER(按B排序,按C排序)不使用(A,B,C)上的索引


12

考虑以下两个功能:

ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)

ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)

据我了解,它们产生的结果完全相同。换句话说,在PARTITION BY子句中列出列的顺序无关紧要。

如果有索引,(A,B,C)我希望优化程序在两个变体中都使用此索引。

但是,令人惊讶的是,优化器决定在第二个变体中进行额外的显式排序。

我已经在SQL Server 2008 Standard和SQL Server 2014 Express上看到了它。

这是我用来复制它的完整脚本。

在Microsoft SQL Server 2014上尝试-12.0.2000.8(X64)2014年2月20日20:04:26版权所有(c)Windows NT 6.1(Build 7601:Service Pack 1)上的Microsoft Corporation Express Edition(64位)

和Microsoft SQL Server 2014(SP1-CU7)(KB3162659)-12.0.4459.0(X64)2016年5月27日15:33:17版权所有(c)Windows NT 6.1(内部版本7601)上的Microsoft Corporation Express Edition(64位):服务包1)

通过使用新旧基数估计OPTION (QUERYTRACEON 9481)OPTION (QUERYTRACEON 2312)

设置表格,索引,样本数据

CREATE TABLE [dbo].[T](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [A] [int] NOT NULL,
    [B] [int] NOT NULL,
    [C] [int] NOT NULL,
    CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_ABC] ON [dbo].[T]
(
    [A] ASC,
    [B] ASC,
    [C] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, 
DROP_EXISTING = OFF, 
ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON)
GO

INSERT INTO [dbo].[T] ([A],[B],[C]) VALUES
(10, 20, 30),
(10, 21, 31),
(10, 21, 32),
(10, 21, 33),
(11, 20, 34),
(11, 21, 35),
(11, 21, 36),
(12, 20, 37),
(12, 21, 38),
(13, 21, 39);

查询

SELECT -- AB
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- BA
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

执行计划

按A,B划分

AB

按B,A划分

BA

都

如您所见,第二个计划还有一个额外的排序。它按B,A,C订购。显然,优化器不够智能,无法意识到与数据PARTITION BY B,A相同PARTITION BY A,B并对其重新排序。

有趣的是,第三个查询具有两个变体,ROW_NUMBER并且没有多余的排序!该计划与第一个查询的计划相同。(序列项目在“输出列表”中有额外的列,但没有额外的排序)。因此,在这种更为复杂的情况下,优化器似乎足够聪明,以至于意识到PARTITION BY B,A与相同PARTITION BY A,B

在第一个和第三个查询中,索引扫描运算符具有属性Ordered:True,在第二个查询中为False。

更有趣的是,如果我这样重写第三个查询(交换两列):

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

然后多余的排序再次出现!

有人可以照亮吗?优化器在这里发生了什么?


Answers:


2

似乎没有一个明确的“答案”来回答“优化器中正在发生的事情”这个问题,除非您是其开发人员并且知道其内部结构。

我将在这里整理评论。

总的来说,将其称为错误似乎太苛刻,因为查询的最终结果是正确的。在某些情况下,执行计划根本不是最佳的。ypercubeᵀᴹMartin SmithAaron Bertrand称其为“缺少优化”。

  • 看起来像GROUP BY a,bGROUP BY b,a产生相同的计划,但PARTITION BY不能使用相同的转换

  • 还有其他一些缺少的优化方法,即如果在选择列表中由具有不同规范的窗口功能分开,则具有相同窗口规范的窗口功能可能会有额外的排序操作。

  • 是的,这似乎是另一个错过的优化方法,其中有很多。优化器是由人编写的,并不完美


有一些相关的文章降序索引。Itzik Ben-Gan的索引排序,并行性和排名计算。Itzik在其中讨论了降序索引,并给出了一个示例,说明索引定义的方向如何影响具有分区的窗口函数。他展示了查询示例和生成的计划,ROW_NUMBER这些查询和生成的计划具有优化程序可以避免的额外排序运算符。


对我而言,实际结果将是牢记优化器的这种特殊性。PARTITION BY在窗口函数中使用时,请始终尝试将列出列PARTITION BY的顺序与在索引中列出的顺序匹配。即使没关系。

此预防措施的另一方面是,当您检查索引并决定在索引定义中交换一些列时。请注意,您可能无意中影响了一些似乎不应该受到影响的现有查询。实际上,这就是我注意到优化器这种特殊性的方式。

否则,优化器可能无法充分利用索引。即使优化器确实选择了一个最佳计划,该计划也可能更改为较不理想的状态,而对查询的更改几乎没有任何改变,例如更改SELECT语句中的列顺序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.