Пав*_*лёв 10 sql-server optimization cross-apply cardinality-estimates query-performance
现在,我面临着基数估计的逻辑问题,这个问题在一个看似相当简单的情况下对我来说不是很清楚。我在工作中遇到过这种情况,因此出于隐私考虑,下面仅对问题进行一般性描述,但为了更详细的分析,我在AdventureWorksDW培训基地中模拟了此问题。
有以下形式的查询:
SELECT <some columns>
FROM <some dates table>
CROSS APPLY(
SELECT
<some p columns>
FROM <some table> p
WHERE p.StartDate <= Dates.d
AND p.EndDate >= Dates.d
) t
Run Code Online (Sandbox Code Playgroud)
从上面给出的执行计划中可以看出,基数估计器估计索引查找操作中的估计行数为 17,884,200(对应于 NL 外部的每行 2,980,700),这与实际数量非常接近.
现在我将修改查询并添加到 CROSS APPLY LEFT OUTER JOIN:
SELECT <some columns t>
FROM <some dates table>
CROSS APPLY(
SELECT
<some p columns>
<some columns f>
FROM <some table> p
LEFT JOIN <some table> f ON p.key = f.key
AND f.date = Dates.d
WHERE p.StartDate <= Dates.d
AND p.EndDate >= Dates.d
) t
Run Code Online (Sandbox Code Playgroud)
此查询给出了以下计划:
看到查询的逻辑形式,可以合理地假设Index Seek操作的预期行数将保持不变,虽然我理解寻找计划的路线不同,但是,似乎部分红色突出显示没有变化,相同的谓词等,但Index Seek的估计现在是664,506(对应于NL外部部分的每行110,751),这是一个严重的错误并且在生产环境中会导致严重的tempdb溢出数据。
上述查询是在 Sql Server 2012 (SP4) (KB4018073) - 11.0.7001.0 (x64) 的实例上执行的。
为了获得更多详细信息并简化分析,我在 SQL Server 2019 (RTM) - 15.0.2000.5 (X64) 实例上的 AdventureWorksDW2017 数据库中模拟了此问题,但我在打开 9481 跟踪标志的情况下执行查询以模拟系统使用基数估计器版本 70。
下面是一个带有左外连接的查询。
DECLARE @db DATE = '20130720'
DECLARE @de DATE = '20130802'
;WITH Dates AS(
SELECT [FullDateAlternateKey] AS d
FROM [AdventureWorksDW2017].[dbo].[DimDate]
WHERE [FullDateAlternateKey] BETWEEN @db AND @de
)
SELECT *
FROM Dates
CROSS APPLY(
SELECT
p.[ProductAlternateKey]
,f.[OrderQuantity]
FROM [AdventureWorksDW2017].[dbo].[DimProduct] p
LEFT JOIN [AdventureWorksDW2017].[dbo].[FactInternetSales] f ON f.ProductKey = p.ProductKey
AND f.[OrderDate] = Dates.d
WHERE p.StartDate <= Dates.d
AND ISNULL(p.EndDate, '99991231') >= Dates.d
) t
OPTION(QUERYTRACEON 9481 /*force legacy CE*/)
Run Code Online (Sandbox Code Playgroud)
还值得注意的是,在 DimProduct 表上创建了以下索引:
CREATE NONCLUSTERED INDEX [Date_Indx] ON [dbo].[DimProduct]
(
[StartDate] ASC,
[EndDate] ASC
)
INCLUDE([ProductAlternateKey])
Run Code Online (Sandbox Code Playgroud)
该查询给出了以下查询计划:(1)
如您所见,以红色突出显示的查询部分估计为 59,754(每行约 182 个)。现在我将演示一个没有左外连接的查询计划。(2)
如您所见,以红色突出显示的查询部分的得分为 97 565(每行 ~ 297),但差异不是很大,但过滤器 (3) 运算符的基数得分明显不同 ~ 每行 244与具有左外连接的查询中的 ~ 54 相比。
(3) – 过滤谓词:
isnull([AdventureWorksDW2017].[dbo].[DimProduct].[EndDate] as [p].[EndDate],'9999-12-31 00:00:00.000')>=[AdventureWorksDW2017].[dbo].[DimDate].[FullDateAlternateKey]
Run Code Online (Sandbox Code Playgroud)
为了更深入地研究,我查看了上述计划中的物理操作员树。
以下是未记录标志 8607 和 8612 跟踪的最重要部分。
对于计划 (2):
PhyOp_Apply lookup TBL: AdventureWorksDW2017.dbo.DimProduct
…
PhyOp_Range TBL: AdventureWorksDW2017.dbo.DimProduct(alias TBL: p)(6) ASC Bmk ( QCOL: [p].ProductKey) IsRow: COL: IsBaseRow1002 [ Card=296.839 Cost(RowGoal 0,ReW 0,ReB 327.68,Dist 328.68,Total 328.68)= 0.174387 ](Distance = 2)
ScaOp_Comp x_cmpLe
ScaOp_Identifier QCOL: [p].StartDate
ScaOp_Identifier QCOL: [AdventureWorksDW2017].[dbo].[DimDate].FullDateAlternateKey
Run Code Online (Sandbox Code Playgroud)
对于计划 (1):
PhyOp_Apply (x_jtInner)
…
PhyOp_Range TBL: AdventureWorksDW2017.dbo.DimProduct(alias TBL: p)(6) ASC Bmk ( QCOL: [p].ProductKey) IsRow: COL: IsBaseRow1002 [ Card=181.8 Cost(RowGoal 0,ReW 0,ReB 327.68,Dist 328.68,Total 328.68)= 0.132795 ](Distance = 2)
ScaOp_Comp x_cmpLe
ScaOp_Identifier QCOL: [p].StartDate
ScaOp_Identifier QCOL: [AdventureWorksDW2017].[dbo].[DimDate].FullDateAlternateKey
Run Code Online (Sandbox Code Playgroud)
如您所见,优化器选择了 Apply 运算符的各种实现,(2) 中的 PhyOp_Apply 查找和 (1) 中的 PhyOp_Apply (x_jtInner),但我仍然不明白我可以从中提取什么。
通过重写没有左外连接的原始查询,我可以获得与计划 (1) 中相同的估计,如下所示:
DECLARE @db DATE = '20130720'
DECLARE @de DATE = '20130802'
;WITH Dates AS(
SELECT [FullDateAlternateKey] AS d
FROM [AdventureWorksDW2017].[dbo].[DimDate]
WHERE [FullDateAlternateKey] BETWEEN @db AND @de
)
SELECT *
FROM Dates
CROSS APPLY(
SELECT TOP(1000000000)
p.[ProductAlternateKey]
FROM [AdventureWorksDW2017].[dbo].[DimProduct] p
WHERE p.StartDate <= Dates.d
AND ISNULL(p.EndDate, '99991231') >= Dates.d
) t
OPTION(QUERYTRACEON 9481 /*force legacy CE*/)
Run Code Online (Sandbox Code Playgroud)
其中给出了以下计划:(4)
如您所见,以红色突出显示的区域的估计与物理运算符树中的计划 (1) 和 PhyOp_Apply (x_jtInner) 运算符一致。
请帮我回答这个问题,有没有办法影响这样的基数估计,可能是通过提示或更改查询形式等,并帮助理解为什么优化器在这种情况下会给出这样的估计。
Pau*_*ite 11
通常有多种方法可以得出基数估计值,每种方法都会给出不同(但同样有效)的答案。这就是统计和估计的本质。
您基本上会问为什么一种方法会产生296.839行的估计值,而另一种方法会产生181.8行。
让我们看一下问题中给出的相同 AdventureWorksDW2017 连接的更简单示例:
DECLARE @db date = '20130720';
DECLARE @de date = '20130802';
SELECT DD.FullDateAlternateKey, DP.ProductAlternateKey
FROM dbo.DimDate AS DD
JOIN dbo.DimProduct AS DP
ON DP.StartDate <= CONVERT(datetime, DD.FullDateAlternateKey)
WHERE
DD.FullDateAlternateKey BETWEEN @db AND @de
OPTION (FORCE ORDER, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
Run Code Online (Sandbox Code Playgroud)
这是以下之间的连接:
DimDate
(过滤FullDateAlternateKey BETWEEN @db AND @de
);和DimProduct
连接谓词是:
DP.StartDate <= CONVERT(datetime, DD.FullDateAlternateKey)
计算连接选择性的一种方法是使用直方图信息考虑FullDateAlternateKey
值将如何与StartDate
值重叠。
的直方图步骤FullDateAlternateKey
将针对 的选择性进行缩放BETWEEN @db AND @de
,然后与 进行比较DP.StartDate
以查看它们如何连接。
使用原始 CE,连接估计将在“连接”之前使用线性插值逐步对齐两个直方图。
一旦我们使用此方法计算了连接的选择性,连接是散列、合并、嵌套循环还是应用都无关紧要(除了显示目的)。
基于直方图的计算步骤并不是特别困难,但它们太冗长,无法在此处显示。所以我将切入正题,简单地展示一下结果:
请注意在搜索中估计的296.839行DimProduct
。
这是连接基数估计计算为 97,565.2 行(使用直方图)的结果。过滤器DimDate
通过 328.68 行,因此内侧必须平均每次迭代产生 296.839 行才能使数学计算出来。
如果此查询可以使用散列或合并连接(由于不等式,它不是),DimProduct
将扫描该表,生成其所有 606 行。连接的结果仍然是 97,565.2 行。
此估计是作为连接进行估计的结果。
我们还可以将此查询估计为apply。用 T-SQL 编写的逻辑等价形式是:
DECLARE @db date = '20130720';
DECLARE @de date = '20130802';
SELECT DD.FullDateAlternateKey, DP.ProductAlternateKey
FROM dbo.DimDate AS DD
CROSS APPLY
(
SELECT DP.ProductAlternateKey
FROM dbo.DimProduct AS DP
WHERE
DP.StartDate <= CONVERT(datetime, DD.FullDateAlternateKey)
) AS DP
WHERE
DD.FullDateAlternateKey BETWEEN @db AND @de
OPTION (FORCE ORDER, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'), QUERYTRACEON 9114);
Run Code Online (Sandbox Code Playgroud)
(跟踪标志 9114 阻止优化器将应用重写为连接)
这次的估计方法是DimProduct
从DimDate
(每次迭代)评估每行将匹配多少行:
我们有328.68行从DimDate
以前一样,但现在每这些行的预期相符181.8行DimProduct
。
这只是对 选择性的猜测StartDate <= FullDateAlternateKey
。
猜测是 606 行的 30% DimProduct
:0.3 * 606 = 181.8行。
该估计是作为应用进行估计的结果。
您的示例引入了外部联接作为使查询过于复杂的一种方式,优化器无法从应用形式转换为联接形式。使用TOP
里面的应用是另一种方式来说服优化不转换的申请加入(即使它可以)。