jes*_*esi 10 performance sql-server sql-server-2014 cardinality-estimates performance-tuning
我试图了解 SQL Server 如何尝试估计 SQL Server 2014 中的“大于”和“大于等于”where 子句。
我想我确实了解基数估计,例如,如果我这样做
select * from charge where charge_dt >= '1999-10-13 10:47:38.550'
Run Code Online (Sandbox Code Playgroud)
基数估计是 6672,可以很容易地计算为 32(EQ_ROWS) + 6624(RANGE_ROWS) + 16 (EQ_ROWS) = 6672(下面截图中的直方图)
但是当我这样做时
select * from charge where charge_dt >= '1999-10-13 10:48:38.550'
Run Code Online (Sandbox Code Playgroud)
(将时间增加到 10:48 所以它不是一步)
估计是 4844.13。
那是怎么计算的?
Pau*_*ite 10
唯一的困难是决定如何处理被查询谓词区间部分覆盖的直方图步骤。如问题中所述,谓词范围涵盖的整个直方图步骤是微不足道的。
F
= 查询谓词所涵盖的步长范围的分数(介于 0 和 1 之间)。
基本思想是使用F
(线性插值)来确定谓词覆盖了多少步内不同值。将此结果乘以每个不同值的平均行数(假设均匀性),并添加步长相等的行数给出基数估计:
基数 = EQ_ROWS + (AVG_RANGE_ROWS * F * DISTINCT_RANGE_ROWS)
相同的公式用于>
和>=
在旧版 CE 中。
新的 CE 稍微修改了之前的算法以区分>
和>=
。
以>
第一,公式为:
基数 = EQ_ROWS + (AVG_RANGE_ROWS * (F * (DISTINCT_RANGE_ROWS - 1)))
因为>=
它是:
基数 = EQ_ROWS + (AVG_RANGE_ROWS * ((F * (DISTINCT_RANGE_ROWS - 1)) + 1))
的+ 1
反映,当比较涉及平等,假设匹配(包含假设)。
在问题示例中,F
可以计算为:
DECLARE
@Q datetime = '1999-10-13T10:48:38.550',
@K1 datetime = '1999-10-13T10:47:38.550',
@K2 datetime = '1999-10-13T10:51:19.317';
DECLARE
@QR float = DATEDIFF(MILLISECOND, @Q, @K2), -- predicate range
@SR float = DATEDIFF(MILLISECOND, @K1, @K2) -- whole step range
SELECT
F = @QR / @SR;
Run Code Online (Sandbox Code Playgroud)
结果是0.728219019233034。将其代入>=
其他已知值的公式中:
基数 = EQ_ROWS + (AVG_RANGE_ROWS * ((F * (DISTINCT_RANGE_ROWS - 1)) + 1)) = 16 + (16.1956 * ((0.728219019233034 * (409 - 1)) + 1)) = 16 + (16.1956 * ((0.728219019233034 * 408) + 1)) = 16 + (16.1956 * (297.113359847077872 + 1)) = 16 + (16.1956 * 298.113359847077872) = 16 + 4828.1247307393343837632 = 4844.1247307393343837632 = 4844.12473073933(浮点精度)
该结果与问题中显示的 4844.13 的估计值一致。
使用旧版 CE(例如使用跟踪标志 9481)的相同查询应该产生以下估计:
基数 = EQ_ROWS + (AVG_RANGE_ROWS * F * DISTINCT_RANGE_ROWS) = 16 + (16.1956 * 0.728219019233034 * 409) = 16 + 4823.72307468722 = 4839.72307468722
请注意,对于>
和>=
使用旧版 CE ,估计值将相同。
当过滤器“大于”或“小于”时,估计行数的公式会变得有点愚蠢,但它是您可以得出的数字。
使用步骤 193,以下是相关数字:
范围行= 6624
EQ_行= 16
AVG_RANGE_ROWS = 16.1956
上一步的 RANGE_HI_KEY = 1999-10-13 10:47:38.550
当前步骤的 RANGE_HI_KEY = 1999-10-13 10:51:19.317
WHERE 子句的值 = 1999-10-13 10:48:38.550
1) 找到两个范围 hi 键之间的 ms
SELECT DATEDIFF (ms, '1999-10-13 10:47:38.550', '1999-10-13 10:51:19.317')
结果是 220767 毫秒。
2)调整行数
我们需要找到每毫秒的行数,但在此之前,我们必须从 RANGE_ROWS 中减去 AVG_RANGE_ROWS:
6624 - 16.1956 = 6607.8044 行
3) 使用调整后的行数计算每毫秒的行数:
6607.8044 行/220767 毫秒 = 0.0299311 行/毫秒
4) 计算 WHERE 子句中的值与当前步 RANGE_HI_KEY 之间的毫秒数
SELECT DATEDIFF (ms, '1999-10-13 10:48:38.550', '1999-10-13 10:51:19.317')
Run Code Online (Sandbox Code Playgroud)
这给了我们 160767 毫秒。
5)根据每秒行数计算本步骤中的行数:
.0299311 行/毫秒 * 160767 毫秒 = 4811.9332 行
6) 还记得我们之前是如何减去 AVG_RANGE_ROWS 的吗?是时候将它们添加回来了。现在我们已经完成了与每秒行数相关的数字的计算,我们也可以安全地添加 EQ_ROWS:
4811.9332 + 16.1956 + 16 = 4844.1288
四舍五入后,这就是我们的估计值 4844.13。
我找不到任何文章或博客文章来解释为什么在计算每毫秒的行数之前减去 AVG_RANGE_ROWS 。我能够确认它们已被纳入估计中,但只是在最后一毫秒——字面意义上的。
使用WideWorldImporters 数据库,我做了一些增量测试,发现行估计的减少是线性的,直到步骤结束,其中突然占了 1x AVG_RANGE_ROWS。
这是我的示例查询:
SELECT PickingCompletedWhen
FROM Sales.Orders
WHERE PickingCompletedWhen >= '2016-05-24 11:00:01.000000'
Run Code Online (Sandbox Code Playgroud)
我更新了 PickingCompletedWhen 的统计数据,然后得到了直方图:
DBCC SHOW_STATISTICS([sales.orders], '_WA_Sys_0000000E_44CA3770')
Run Code Online (Sandbox Code Playgroud)
为了了解当我们接近 RANGE_HI_KEY 时估计行数如何减少,我在整个步骤中收集了样本。减少是线性的,但其行为就好像等于 AVG_RANGE_ROWS 值的行数不是趋势的一部分......直到您达到 RANGE_HI_KEY ,它们突然像未收回的债务注销一样下降。您可以在示例数据中看到它,尤其是在图表中。
请注意,行数稳步下降,直到我们达到 RANGE_HI_KEY,然后突然减去最后一个 AVG_RANGE_ROWS 块。在图表中也很容易发现。
总而言之,AVG_RANGE_ROWS 的奇怪处理使得计算行估计变得更加复杂,但您始终可以协调 CE 正在执行的操作。
指数退避是新的(从 SQL Server 2014 开始)基数估计器在使用多个单列统计信息时用来获得更好估计的方法。由于这个问题是关于一个单列统计数据,所以它不涉及EB公式。