优化器未选择索引联合计划

pet*_*ter 7 sql-server optimization execution-plan

为什么下面的查询很慢?

select count(*) 

from [dbo].[mt_dispatch_link] 
   , [dbo].[_mt_dispatch] [_mt_dispatch]

where   (mt_dispatch_link.contract_id_1 = _mt_dispatch.contract_id 
     and mt_dispatch_link.dispatch_id_1 = _mt_dispatch.dispatch_id)

   or   (mt_dispatch_link.contract_id_2 = _mt_dispatch.contract_id 
     and mt_dispatch_link.dispatch_id_2 = _mt_dispatch.dispatch_id)
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

这需要 10 多分钟,然后我倾向于在那个时候停止它。我的问题更多是关于如何理解查询计划。

查看查询计划,我可以看到底部聚集索引扫描返回大约 250000 条记录,但成本为 0% 并且它正在放入临时表中。

顶部索引扫描大约是 25000 条记录。

但是 95% 的成本来自嵌套连接。我应该从中得出什么结论?

上面的查询计划显示了两次索引扫描,是说它在做 25000 + 250000 次索引扫描,还是说它在做 25000 * 250000 次索引扫描?

如果我将查询更改为此(添加FORCESEEK):

select count(*) 

from [dbo].[mt_dispatch_link] 
   , [dbo].[_mt_dispatch] [_mt_dispatch] 

    WITH (FORCESEEK)

where   (mt_dispatch_link.contract_id_1 = _mt_dispatch.contract_id 
     and mt_dispatch_link.dispatch_id_1 = _mt_dispatch.dispatch_id)

   or   (mt_dispatch_link.contract_id_2 = _mt_dispatch.contract_id 
     and mt_dispatch_link.dispatch_id_2 = _mt_dispatch.dispatch_id)
Run Code Online (Sandbox Code Playgroud)

我最终得到了一个更好的计划,查询立即运行:

在此处输入图片说明

我在两个表上运行了更新统计信息。可惜没修好。表设计不是很好,所以我认为 SQL Server 并不真正理解,因此提出了一个糟糕的查询计划。有关如何优化查询的表设计的更多信息。

为什么查询优化器没有提出最佳计划?

Pau*_*ite 13

优化器并不总是考虑索引联合计划(如第二张图中所示的计划)来解决析取(OR谓词),除非指定了 aFORCESEEKINDEXhint。这是基于一些实际考虑的启发式*:

  1. 索引联合通常不足以作为一般查询的良好计划选择。
  2. 索引可以组合的方式数量呈指数增长。

使用提示会改变优化器搜索可能计划空间的方式。它禁用了一些一般的启发式方法,并追求更以目标为导向的策略。

优化器通常的主要目标是快速找到一个好的计划。它不会详尽地搜索“最佳”计划(即使是相对简单的查询也可能需要数年时间才能编译)。

多个条件分隔的连接OR长期以来一直存在问题。多年来,优化器添加了新技巧,例如将它们转换为等效UNION形式,但可用的转换是有限的,因此很容易解脱。

就查询计划而言:

  1. DispatchLink 的第一行导致对 Dispatch 表的完整扫描
  2. 扫描结果存储在内部tempdb工作表(Table Spool)中
  3. 连接根据完整OR谓词检查工作表中的每一行
  4. 从 DispatchLink 获取下一行,并从步骤 3 开始重复该过程

如果 Dispatch Link 表中有 25,000 行,则将完全扫描假脱机 25,000 次。这当然是一场灾难(如果没有索引交叉,优化器能做的最好的事情就是在多个线程上运行整个事情)。

查询计划中的百分比成本只是优化器的估计。它们从不反映实际执行成本,并且受优化器模型的影响,并且通常与在特定硬件上执行计划的“真实”成本几乎没有相似之处。

成本计算数字是为了提供信息,但不应按字面意思理解。优化器使用的特定模型恰好为世界上大多数系统上的大多数查询生成了非常好的计划——这并不意味着该模型接近任何人的现实,只是它在实践中碰巧工作得相当好。

更改设计以便 (Dispatch, Contract) 对存储在行中而不是跨列重复,这将使整个索引交叉问题消失。具有有用约束和索引的关系设计几乎总能从优化器中获得最佳效果。


* 这可以用未记录的跟踪标志 8726 覆盖

  • 这对我来说是一个真正的洞察力。谢谢。从关系的角度来看,老实说,我们有一个非常混乱的数据库,因此需要进行大量结构更改。理解这些东西是关键。 (3认同)