如何让交叉应用在视图上逐行操作?

cro*_*sek 7 sql-server execution-plan view cross-apply

我们有一个针对单项查询优化的视图(200 毫秒无并行性):

select * 
    from OptimizedForSingleObjectIdView e2i
   where ObjectId = 3374700
Run Code Online (Sandbox Code Playgroud)

它也适用于一小组静态 ID(~5)。

select * 
    from OptimizedForSingleObjectIdView e2i
   where ObjectId in (3374700, 3374710, 3374720, 3374730, 3374740);
Run Code Online (Sandbox Code Playgroud)

但是,如果对象来自外部源,那么它会生成一个缓慢的计划。执行计划显示视图部分的执行分支忽略了 ObjectId 上的谓词,而在原始情况下,它使用它们来执行索引查找。

select v.*
  from 
     (
       select top 1 ObjectId from Objects
        where ObjectId % 10 = 0
        order by ObjectId
     ) o  
  join OptimizedForSingleObjectIdView v -- (also tried inner loop join)
    on v.ObjectId = o.ObjectId;
Run Code Online (Sandbox Code Playgroud)

我们不希望投资于“双重”优化非奇异情况的视图。相反,我们“寻求”的解决方案是对每个对象重复调用一次视图,而无需求助于 SP

大多数情况下,以下解决方案会逐行调用视图。但是这次不是,甚至不是只有 1 个对象:

select v.*
  from
     (
       select top 1 ObjectId 
         from Objects 
        where ObjectId % 10 = 0 -- non-trivial predicate
        order by ObjectId
     ) o
   cross apply
    (
      select top 2000000000 *
        from OptimizedForSingleObjectIdView v_
       where ObjectId = o.ObjectId 
       order by v_.SomeField
    ) v;
Run Code Online (Sandbox Code Playgroud)

有一次,我认为有一种说法是,当它调用 UDF 时,可以保证按行执行交叉应用,但这也失败了:

create function FunctionCallingView(@pObjectId bigint)
returns table
as 
return select *
  from OptimizedForSingleObjectIdView 
 where ObjectId = @pObjectId;             

select v.*
  from
     (
       select top 1 ObjectId 
         from Objects 
        where ObjectId % 10 = 0
        order by ObjectId
     ) o
 cross apply FunctionCallingView(o.ObjectId) v
Run Code Online (Sandbox Code Playgroud)

添加选项(强制顺序)没有帮助——但是视图中已经有两个哈希提示。暂时移除它们并没有帮助并减慢了单个案例的速度。

这是基于函数的慢速案例的估计计划的片段。1 行的估计是正确的。最右边(未显示)是一个不包括前 1 个结果的搜索谓词。这似乎与我们遇到的其他情况类似,其中来自表查找的奇异探测值不用作其他地方的查找谓词。

在此处输入图片说明

Pau*_*ite 10

如果不使用引入新 T-SQL 执行范围的东西,例如非内联(多语句)表值函数,就不可能完全保证评估外部查询的每一行的视图BEGIN...END。这几乎是在回答您之前的问题如何使用合并提示来隔离 SQL Server 中的复杂查询时给出的建议。

有一次,我认为有一种说法是,当它调用 UDF 时,可以保证逐行执行交叉应用

这不适用于内联表值函数,因为在优化开始之前,定义已扩展到调用查询中。


也就是说,您可以做一些事情来强烈鼓励预期的结果。

执行计划显示视图部分的执行分支忽略谓词 onObjectId而在原始情况下它使用它们来执行索引查找。

您期望ObjectId使用每个驱动行的索引查找“在视图内”评估值。这是相关的嵌套循环连接(应用)执行方式。请注意,使用APPLYT-SQL 语言元素并不能保证物理执行将使用应用样式。

听起来好像 SQL Server 选择使用ObjectIdNested Loops Join运算符中测试的值来执行。这是一个不相关的或简单的嵌套循环连接执行模式。

这很可能是由您在视图中使用的连接提示引起的。通常应避免使用联接提示,因为它们极大地限制了优化器的自由度,而不仅仅是联接的物理类型。特别是,连接提示还强制整个查询的连接顺序(就像您使用了FORCE ORDER提示一样)并阻止与聚合放置和策略相关的多项优化,以及除此之外的许多其他优化。

如果您确实必须在视图中加入连接提示(我强烈建议您通常避免这种情况),您可能会发现获得所需计划形状的最可靠方法是:

  1. 使用视图定义(不引用视图)创建内联( RETURNS TABLE) 函数。
  2. @ObjectId作为函数的参数提供。
  3. 在函数体内使用参数放置谓词,使索引查找更有可能。
  4. 一个FORCESEEK表提示里面的函数可以使用,如果真的每次使用应导致所追求的。
  5. 使用 调用新的内联函数APPLY

我通常不喜欢使用特定的语法和提示来试图强制某种物理计划形状。通过参数化查询并使用计划指南保证计划形状,您可能会取得更大的成功。