我很难理解为什么SQL Server会提出一个很容易证明与统计数据不一致的估计。
一致性
不能保证一致性。可以使用不同的统计方法在不同时间在不同(但逻辑上等效)子树上计算估计值。
说将这两个相同的子树连接起来应该产生叉积的逻辑没有错,但是同样也没有说推理的选择比其他任何选择都合理的逻辑。
初步估计
在您的特定情况下,不对两个相同的子树执行联接的初始基数估计。当时的树形为:
LogOp_Join
LogOp_GbAgg
LogOp_LeftOuterJoin
LogOp_Get TBL:ar
LogOp_Select
LogOp_Get TBL:tcr
ScaOp_Comp x_cmpEq
ScaOp_Identifier [tcr] .rId
ScaOp_Const值= 508
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .fId
ScaOp_Identifier [tcr] .fId
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .bId
ScaOp_Identifier [tcr] .bId
AncOp_PrjList
AncOp_PrjEl Expr1003
ScaOp_AggFunc stopMax
ScaOp_Convert int
ScaOp_Identifier [tcr] .isS
LogOp_Select
LogOp_GbAgg
LogOp_LeftOuterJoin
LogOp_Get TBL:ar
LogOp_Select
LogOp_Get TBL:tcr
ScaOp_Comp x_cmpEq
ScaOp_Identifier [tcr] .rId
ScaOp_Const值= 508
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .fId
ScaOp_Identifier [tcr] .fId
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .bId
ScaOp_Identifier [tcr] .bId
AncOp_PrjList
AncOp_PrjEl Expr1006
ScaOp_AggFunc stopMin
ScaOp_Convert int
ScaOp_Identifier [ar] .isT
AncOp_PrjEl Expr1007
ScaOp_AggFunc stopMax
ScaOp_Convert int
ScaOp_Identifier [tcr] .isS
ScaOp_Comp x_cmpEq
ScaOp_Identifier Expr1006
ScaOp_Const值= 1
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[ar] .fId
ScaOp_Identifier QCOL:[ar] .fId
第一输入连接已经有一个未投影聚集体简化了,并且第二连接输入具有谓词t.isT = 1
推它下面,其中t.isT
是MIN(CONVERT(INT, ar.isT))
。尽管如此,该isT
谓词的选择性计算仍可以CSelCalcColumnInInterval
在直方图中使用:
CSelCalcColumnInInterval
列:COL:Expr1006
已从ID为3的统计信息中的QCOL列加载的直方图:[ar] .isT
选择性:4.85248e-005
统计收集生成:
CStCollFilter(ID = 11,CARD = 1)
CStCollGroupBy(ID = 10,CARD = 20608)
CStCollOuterJoin(ID = 9,CARD = 20608 x_jtLeftOuter)
CStCollBaseTable(ID = 3,CARD = 20608 TBL:ar)
CStCollFilter(ID = 8,CARD = 1)
CStCollBaseTable(ID = 4,CARD = 28 TBL:tcr)
(正确的)期望是该谓词将20,608行减少为1行。
联合估算
现在的问题是,来自另一个联接输入的20,608行将如何与这一行匹配:
LogOp_Join
CStCollGroupBy(ID = 7,CARD = 20608)
CStCollOuterJoin(ID = 6,CARD = 20608 x_jtLeftOuter)
...
CStCollFilter(ID = 11,CARD = 1)
CStCollGroupBy(ID = 10,CARD = 20608)
...
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[ar] .fId
ScaOp_Identifier QCOL:[ar] .fId
通常,有几种不同的方法可以估算联接。例如,我们可以:
- 在每个子树中的每个计划运算符处派生新的直方图,在连接处对齐它们(根据需要插入步长值),并查看它们如何匹配;要么
- 对直方图执行更简单的“粗略”对齐(使用最小值和最大值,而不是逐步进行);要么
- 单独为联接列计算单独的选择性(从基表中进行,无需任何过滤),然后添加非联接谓词的选择性效果。
- ...
根据所使用的基数估计量和一些启发式方法,可以使用任何这些(或变体)。有关更多信息,请参阅《 Microsoft白皮书使用SQL Server 2014基数估计器优化查询计划》。
虫子?
现在,如问题所述,在这种情况下,“简单”单列联接(在上fId
)使用CSelCalcExpressionComparedToExpression
计算器:
计算计划:
CSelCalcExpressionComparedToExpression [ar] .fId x_cmpEq [ar] .fId
列QCOL:[ar] .bId(ID为2的统计信息)的已加载直方图
已加载ID为1的QCOL列的直方图:[ar] .fId
选择性:0
此计算估计将20608行与1个过滤的行连接在一起将具有零选择性:没有行将匹配(在最终计划中报告为一行)。错了吗 是的,这里的新CE可能存在错误。有人可能会争辩说1行将匹配所有行或不匹配任何行,因此结果可能是合理的,但是有理由相信其他情况。
细节实际上是相当棘手的,但是期望基于未过滤的fId
直方图进行估计,并通过过滤器的选择性进行修改,从而给20608 * 20608 * 4.85248e-005 = 20608
出行是非常合理的。
执行此计算将意味着使用计算器CSelCalcSimpleJoinWithDistinctCounts
而不是CSelCalcExpressionComparedToExpression
。没有记录的方法可以执行此操作,但是如果您好奇,可以启用未记录的跟踪标志9479:
请注意,最后的联接从两个单行输入产生20,608行,但这并不奇怪。它与原始CE根据TF 9481制定的计划相同。
我提到细节是棘手的(调查很费时),但是据我所知,问题的根本原因与谓词有关rId = 508
,选择性为零。该零估计值以通常的方式上升到一行,当它考虑输入树中的较低谓词时,这似乎有助于所讨论的连接处的零选择性估计(因此有的加载统计信息bId
)。
允许外部联接保持零行内部估算(而不是提高到一行)(以便所有外部行都合格),可以使用任一计算器提供“无错误”的联接估算。如果您对此感兴趣,则未记录的跟踪标志为9473(单独):
连接基数估计的行为CSelCalcExpressionComparedToExpression
也可以修改为不考虑带有另一个未记录的变化标志的``bId''(9494)。我提到所有这些是因为我知道您对这些事情感兴趣。不是因为他们提供了解决方案。除非您向Microsoft报告此问题,然后他们解决(或不解决),否则以不同的方式表达查询可能是最好的解决方法。不管行为是否是故意的,他们都应该对回归有兴趣。
最后,整理一下复制脚本中提到的另一件事:问题计划中Filter的最终位置是基于成本的探索结果,GbAggAfterJoinSel
将聚合和filter移到了join之上,因为join输出很小行数。如您所料,过滤器最初位于联接下方。