从带有外部参数化“where”子句的视图中调用时,窗口函数会导致糟糕的执行计划

GSe*_*erg 10 sql-server-2008 view window-functions

很久以前我遇到过这个问题,我找到了一个适合我的解决方法并忘记了它。

但是现在在 SO 上有这个问题,所以我愿意提出这个问题。

有一个视图以非常简单的方式(订单 + 订单行)连接几个表。

在没有where子句的情况下查询时,视图返回几百万行。
然而,从来没有人这样称呼它。通常的查询是

select * from that_nasty_view where order_number = 123456;
Run Code Online (Sandbox Code Playgroud)

这将返回 5m 中的大约 10 条记录。

一件重要的事情:视图包含一个窗口函数,rank(),它由始终查询视图所使用的字段精确分区:

rank() over (partition by order_number order by detail_line_number)
Run Code Online (Sandbox Code Playgroud)

现在,如果使用查询字符串中的文字参数查询此视图,完全如上所示,它会立即返回行。执行计划很好:

  • 使用索引对两个表进行索引查找order_number(返回 10 行)。
  • 在返回的微小结果上计算窗口。
  • 选择。

然而,当以参数化的方式调用视图时,事情变得很糟糕:

  • Index scan在所有表上忽略索引。返回 5m 行。
  • 巨大的加入。
  • 计算所有partitions 的窗口(大约 500k 个窗口)。
  • Filter 从 5m 中取出 10 行。
  • 选择

在涉及参数的所有情况下都会发生这种情况。它可以是 SSMS:

declare @order_number int = 123456;
select * from that_nasty_view where order_number = @order_number;
Run Code Online (Sandbox Code Playgroud)

它可以是 ODBC 客户端,例如 Excel:

select * from that_nasty_view where order_number = ?
Run Code Online (Sandbox Code Playgroud)

或者它可以是使用参数而不是 sql 连接的任何其他客户端。

如果窗口函数从视图中移除,它运行得非常快,不管它是否使用参数进行查询。

我的解决方法是删除有问题的函数并在稍后阶段重新应用它。

但是,什么给了?它真的是 SQL Server 2008 处理窗口函数的错误吗?

Mar*_*ith 5

这似乎是一个长期存在的问题,它不断以一种或另一种形式重新出现,并且仍然存在于 SQL Server 2012 中。

一些讨论它的帖子是

直到(包括 2012)的所有当前 SQL Server 版本都无法将分区组上的过滤器推送到参数化谓词的序列项目之外,除非option(recompile)使用了 if(如果 2008+)。

recompile提示的替代方法是重写查询以使用@a1ex07建议的参数化内联 TVF )


a1e*_*x07 3

我会尝试用表值 udf 替换视图。这样它将首先过滤记录,然后应用窗口函数。该函数可以接受表参数,因此您可以将多个参数传递order_number给它