我在一台服务器上有一个查询,优化器估计其成本为 0.01。实际上,它最终运行得非常糟糕。
- 它最终执行聚簇索引扫描
注意:您可以在 Stackoverflow 上找到详细的 ddl、sql、表等。但是这些信息虽然很有趣,但在这里并不重要——这是一个不相关的问题。而且这道题连DDL都不需要。
如果我强制使用覆盖索引查找,它估计使用该索引的子树成本为 0.04。
- 聚簇索引扫描:0.01
- 覆盖指数扫描:0.04
因此,服务器选择使用以下计划也就不足为奇了:
- 实际上导致聚集索引的 147,000 次逻辑读取
- 而不是更快的 16 次覆盖索引读取
服务器A:
| Plan | Cost | I/O Cost | CPU Cost |
|--------------------------------------------|-----------|-------------|-----------|
| clustered index scan (optimizer preferred) | 0.0106035 | 116.574 | 5.01949 | Actually run extraordinarily terrible (147k logical reads, 27 seconds)
| covering index seek (force hint) | 0.048894 | 0.0305324 | 0.0183616 | actually runs very fast (16 logical reads, instant)
这与使用 FULLSCAN 更新的统计数据一样。
在另一台服务器上试试
所以我尝试在另一台服务器上。我得到了相同查询的估计值,其中包含生产数据库的最新副本,以及最新的统计信息(WITH FULLSCAN)。
- 这另一台服务器也是 SQL Server 2014
- 但它正确地意识到聚簇索引扫描是不好的
- 它自然更喜欢覆盖索引查找(因为成本低 5 个数量级!)
服务器乙:
| Plan | Cost | I/O Cost | CPU Cost |
|-------------------------------------------|-------------|------------|-----------|
| Clustered index scan (force hint) | 115.661 | 110.889 | 4.77115 | Runs extraordinarily terrible as server A (147k logical reads, 27 seconds)
| Covering index seek (optimizer preferred) | 0.0032831 | 0.003125 | 0.0001581 | Runs fast (16 logical reads, near instant)
我不明白的是,为什么这两台服务器的数据库副本几乎相同,都具有最新的统计信息,而且都是 SQL Server 2014:
- 可以如此正确地运行查询
- 另一个摔死了
我知道这似乎是统计数据过时的经典案例。或缓存的执行计划,或参数嗅探。但是这些测试查询都是用 发出的OPTION(RECOMPILE)
,例如:
SELECT MIN(RowNumber) FROM Transactions
WITH (index=[IX_Transactions_TransactionDate]) WHERE TransactionDate >= '20191002 04:00:00.000' OPTION(RECOMPILE)
仔细一看,好像“运营商”的估计是错误的
聚簇索引扫描是一件坏事。其中一台服务器知道这一点。这是一个非常昂贵的操作,扫描操作应该告诉我这一点。
如果我强制执行聚簇索引扫描,并查看两台服务器上的估计扫描操作,我会突然想到:
| Cost | Server A | Server B |
|---------------------|-------------|------------|
| I/O Cost | 116.573 | 110.889 |
| CPU Cost | 5.01945 | 4.77155 |
| Total Operator Cost | 0.0106035 | 115.661 |
mistakenly | avoids it
uses it |
服务器A上的操作员成本太低了。
- I /O成本合理
- CPU成本合理
- 但总的来说,运营商的总体成本低了 4 个数量级。
这就解释了为什么它错误地选择了糟糕的执行计划;它的运营成本很低。服务器 B 正确地找出了它,并避免了聚簇索引扫描。
不是operator = cpu + io吗?
在几乎每个执行计划节点上,您都会将鼠标悬停在上面,在 dba、stackoverflow 和每个博客上执行计划的每个屏幕截图上,您都会看到:
operatorCost >= max(cpuCost, ioCost)
事实上它通常是:
operatorCost = cpuCost + ioCost
那么这是怎么回事?
什么可以解释服务器决定 115 + 5 的成本几乎为零,而是决定成本的 1/10000?
我知道 SQL Server 有选项可以调整应用于 CPU 和 I/O 操作的内部权重:
DBCC TRACEON (3604); -- Show DBCC output
DBCC SETCPUWEIGHT(1E0); -- Default CPU weight
DBCC SETIOWEIGHT(0.6E0); -- I/O multiplier = 0.6
DBCC SHOWWEIGHTS; -- Show the settings
当您这样做时,运算符成本最终可能会低于 CPU+I/O 成本:
但是没有人玩过这些。有没有可能 SQL Server 有一些基于环境的自动权重调整,或者基于与磁盘子系统的一些通信?
如果服务器是虚拟机,使用虚拟 SCSI 磁盘,通过光纤链路连接到存储区域网络 (SAN),它可能会决定可以忽略 CPU 和 I/O 成本吗?
除了它不能是此服务器中的永久环境之外,因为我发现的所有其他查询都表现正常:
I/O: 0.0112613
CPU: +0.0001
=0.0113613 (theoretical)
Operator: 0.0113613 (actual)
什么可以解释服务器不占用:
I/O Cost + Cpu Cost = Operator Cost
在这个例子中正确吗?
SQL 服务器 2014 SP2。
行目标
如果在查询中设置了行目标,这可能会影响行估计和成本计算。
您可以通过在启用跟踪标志 4138 的情况下运行查询(这将消除行目标的影响)来确认这是否是导致问题的原因。
缓冲池大小
如果有更大的可用缓冲池(成本降低的服务器有 14 GB RAM,而另一台机器有 6 GB),一些 I/O 操作的估计成本可以降低。
您可以通过在计划 XML 中查找“EstimatedPagesCached”来检查此行为的影响。此属性的较高值可以降低可能访问相同数据的执行计划部分的 I/O 成本。
可用调度程序
对于并行查询,运算符的 CPU 成本可以减少“# of schedulers / 2”。您可以通过在计划 XML 中查找“EstimatedAvailableDegreeOfParallelism”来检查它的值。
我提到这一点是因为我注意到“慢速查询”在具有 4 个内核的服务器上运行,而更快的查询在具有 1 个内核的服务器上运行。
成本是奇怪的和破碎的
Forrest 在他的博客上谈到了一系列导致成本最终变得毫无意义的不同方式:Percentage Non Grata
这取决于。
很遗憾其他人删除了他们的帖子,因为我提出了类似的想法。
行目标
这不是您根据屏幕截图所遇到的情况,但这是计算操作员成本的一个因素。I/O 和 CPU 成本不会缩放,如果行目标未生效,它们将显示每次执行成本。运算符成本确实会缩放以显示行目标。这是 I/O 和 CPU 不完全包含 Operator 成本的一个实例,需要考虑估计的执行次数。您如何查看这些统计数据取决于您查看的是内部输入还是外部输入。
来源:优化器内部:Paul White 深入了解行目标 - 2010 年 8 月 18 日 (存档)
缓冲池使用
这可能是影响您的一个因素。
资料来源:Joe Chang 的执行计划成本模型 - 2009 年 7 月 (存档)
关于你的问题
我们可以在您的屏幕截图中看到您在服务器上有一个非常有趣的子树成本表现不佳。有趣的是它可以使用更多的内存和更少的 CPU。
以上信息向我表明,您可能在子树成本方面存在问题,而运算符成本是一个症状。
资料来源:Grant Fritchey 的实际执行计划成本 - 2018 年 8 月 20 日 (存档)
我认为答案就在这些句子中:
我认为发生在你身上的事情:
否则,我很乐意看到估计的和实际的查询计划,以更深入地了解正在发生的事情。
重要的是,如果你在生产环境中运行它而不了解会发生什么并且没有计划它,这将会伤害(你可能会被解雇)。这就是我清除缓存以通过重新编译再次测试的方式。
Bhavesh Patel 刷新或清除 SQL Server 缓存的不同方法 - 2017 年 3 月 31 日 (存档)
DBCC FREESYSTEMCACHE
DBCC FREESESSIONCACHE
DBCC FREEPROCCACHE
对我来说,服务器 A 选择聚集索引扫描似乎是绝对正常的。考虑到优化器的知识,这是最好的决定。奇怪的是服务器B并没有选择相同的。我想我对此有一个答案,但首先让我解释一下为什么优化器必须选择聚簇索引扫描。
基本原因与它认为 RowNumber 和 TransactionDate 中的值是独立的这一事实有关。正如它在这里所说:
查询是
选项是:1) 开始扫描按 RowNumber 排序的聚簇索引,并在遇到第一个 TransactionDate >= '20191002 04:00:00.000' 的元组时立即停止,这将是查询的实际答案2) 在 TransactionDate 的非聚集索引中搜索值“20191002 04:00:00.000”,然后继续从该值开始扫描索引的其余部分,保持它将找到的最小 RowNumber
我在这里假设值“20191002 04:00:00.000”是 TransactionDate 列中的最大值之一。实际上,我们假设它大于 95% 的值。鉴于独立性假设,在选项 1 中,可以合理地假设答案将在一次磁盘提取中找到,因为每个扫描的元组都有 5% 的概率是最终答案。在选项2中,搜索特定日期的索引,已经涉及更多的磁盘页面获取,然后我们还必须扫描索引的5%。但实际上,由于两列中的值直接相关,优化器认为最好的选择最终会扫描 95% 的聚簇索引。
那么,为什么Server B不选择扫描聚集索引呢?显然,在服务器 B 中,聚集索引未按 RowNumber 排序,正如我们从原始问题中发布的计划中看到的那样:
那么,为什么 CPU_cost + I/O_cost >> 成本。似乎用于聚集索引扫描的 SQL Server 报告了全表 CPU 和 I/O 成本,即使它只是部分扫描,也只报告基于它找到预期值的速度的实际估计值作为总成本。您可以在此处发布的计划中看到完全相同的行为
至于可以做什么,如果 RowNumber 和 TransactionDate 总是增加,查询可以重写如下:
我们可以假设服务器是真正相同的吗?
我注意到在修改 sql2012 服务器上的数据库兼容性级别后,为 SP 执行计划返回的查询步骤成本发生了微小变化。(空闲数据库,获得第一个计划 xml,应用选项更改,重新编译 sp,获得第二个计划 xml)计划本身看起来相同。优化器中有更多选项可用,计算方式可能略有不同。如果你在 2x 服务器上有不同的补丁/兼容性,它可能会导致实际计划更加根本不同(错误..)