SQL 服务器视图异常缓慢

Unusual slowness of an SQL Server View

我正在编写一个查询第三方应用程序数据库的应用程序。

我的应用程序在第三方数据库中创建了 "library" 个 SQL 视图,因此我以后的查询更容易编写和阅读。 (不仅因为我的应用程序的域逻辑,还因为第三方数据库对表和列使用了糟糕的名称。)

我注意到我的一个视图(加入其他视图,又加入其他视图...)在客户端系统上显示异常缓慢。我将它分解成更小的部分,但无法找出任何明显的嫌疑人。

以下是视图的缩小版本,它通常引用的其他视图变成了 CTE,它仍然和原始视图一样慢。但是,如果我将它分解成更小的部分,它们每个都执行得非常快。我还添加了一些评论,展示了使查询速度更快的小改动示例。

-- The query takes about 5s when the server has no other load
-- That's too slow because the UI of the app needs the results
with
orderLines as (
    select r.DocEntry as rdrDocId,
           r1.LineNum as rdrLineId
    from rdr1 r1
    join ordr r on r.DocEntry = r1.DocEntry
    -- If I filter only by LineStatus or only by DocStatus here, query takes <1s
    where r1.LineStatus = 'O' and r.DocStatus = 'O'
),
picklistDetails as (
    select U_KommNr as pklDocId,
           max(cast(U_Space as int)) as maxPlace
    from [@PICKING]
    where U_DeletedF = 'N'
    group by U_KommNr
),
picklistDocs as (
    select p.AbsEntry      as pklDocId,
           case
           when pd.maxPlace is null then 0
           else pd.maxPlace
           end             as pklDocMaxPlace
    from opkl p
    left join picklistDetails pd on pd.pklDocId = p.AbsEntry
),
picklistDocLines as (
    select AbsEntry   as pklDocId,
           PickEntry  as pklLineId,
           OrderEntry as rdrDocId,
           OrderLine  as rdrLineId
    from PKL1
)
select p.pklDocMaxPlace
from picklistDocs p
join picklistDocLines p1 on p.pklDocId = p1.pklDocId
join orderLines r1 on r1.rdrDocId = p1.rdrDocId
                  and r1.rdrLineId = p1.rdrLineId
-- If I force parallelism by using the following option, query takes <1s
--option(querytraceon 8649)

除了查询的所有部分单独执行都非常快这一事实之外,当我使用 #temp 表而不是 CTE 时,我的执行时间也快得多(总共 <1 秒),例如以下:

-- This batch execution returns the same result but takes <1s

select r.DocEntry as rdrDocId,
        r1.LineNum as rdrLineId
into #orderLines
from rdr1 r1
join ordr r on r.DocEntry = r1.DocEntry
where r1.LineStatus = 'O' and r.DocStatus = 'O'

select U_KommNr as pklDocId,
       max(cast(U_Space as int)) as maxPlace
into #picklistDetails
from [@PICKING]
where U_DeletedF = 'N'
group by U_KommNr

select p.AbsEntry      as pklDocId,
       case
       when pd.maxPlace is null then 0
       else pd.maxPlace
       end             as pklDocMaxPlace
into #picklistDocs
from opkl p
left join #picklistDetails pd on pd.pklDocId = p.AbsEntry

select AbsEntry   as pklDocId,
       PickEntry  as pklLineId,
       OrderEntry as rdrDocId,
       OrderLine  as rdrLineId
into #picklistDocLines
from PKL1

select p.pklDocMaxPlace
from #picklistDocs p
join #picklistDocLines p1 on p.pklDocId = p1.pklDocId
join #orderLines r1 on r1.rdrDocId = p1.rdrDocId
                   and r1.rdrLineId = p1.rdrLineId

任何人都可以理解 SQL 服务器的行为吗?对我来说,这似乎有点像查询优化器的错误/失败。

如果我找不到使视图尽可能快的方法,我可能会把它变成一个使用 #temp 表的过程,就像我粘贴的第二个代码一样,但最好我想避免这种情况。我有几十个具有相似复杂性的视图,none 太慢了。

Can anyone make sense of the behavior of SQL Server here? To me it seems kind of like a bug/failure of the query optimizer.

不,这不是错误。

任务分成几个较小的单元:

暂时的table方法无非是将那个大的查询计划拆分成更小的部分并独立执行。

片段越小,SQL Server Query Optimizer 不会在基数估计中执行一些显着不匹配的机会就越大,并且会选择正确的物理运算符和连接类型,因此看到嵌套的机会越小遍历数百万行或其他一些讨厌的东西。

当有一段运行下面所述的代码时,查询优化器知道每个涉及的临时文件中有多少行table以及它们是如何分布的:

select p.pklDocMaxPlace
from #picklistDocs p
join #picklistDocLines p1 on p.pklDocId = p1.pklDocId
join #orderLines r1 on r1.rdrDocId = p1.rdrDocId
                   and r1.rdrLineId = p1.rdrLineId

一个工作单元:

Lukasz 和 Robert 在评论中提到的 CTE 方法是一种语法糖,类似于视图上的视图。然而,最终,查询优化器必须将所有 CTE 扁平化为一个合并的、有时是大型的查询计划,并将其作为 一个单元 执行。因此,计划越大,出现与性能相关的意外的可能性就越大。

因此,与之前的代码片段相比,查询优化器在行数刚好通过使用统计信息的基数估计猜测的那一刻编译计划:

select p.pklDocMaxPlace
from picklistDocs p
join picklistDocLines p1 on p.pklDocId = p1.pklDocId
join orderLines r1 on r1.rdrDocId = p1.rdrDocId
                  and r1.rdrLineId = p1.rdrLineId

querytraceon 8649:

当您启用 option(querytraceon 8649) 时,您只需强制查询优化器更改行为,就像其他查询提示或类似于 4199 的跟踪一样。 如此强制并​​行,也许偶尔会产生一个更好的计划,但你很难依赖这个。

关于如何解决的一些想法:

  • 有关 tables
  • 的统计更新
  • 尝试切换新旧基数估计器
  • (imho) 将 CTE 重写为派生的 table?
  • 如果涉及大型数据集,那么使用#temp table 方法将逻辑拆分成更小的部分是一种可以选择的一致解决方法。
  • 等等等等

有一个例外:

  • 索引视图。通过使用提示 NOEXPAND(或者如果使用企业版)。视图的逻辑不应该被扁平化到涉及它的查询的整体查询计划中。