SQL Server视图异常缓慢

时间:2019-03-06 18:10:56

标签: sql-server tsql sql-server-2014

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

我的应用程序在第三方数据库中创建了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时,我的执行时间也快得多(总计<1s),如下所示:

-- 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 Server的行为吗?在我看来,这似乎是查询优化器的错误/失败。

如果我找不到使视图尽可能快的方法,则可能会将其转换为使用#temp表的过程,就像粘贴的第二个代码一样,但最好我想避免这种情况。我有数十种具有相似复杂性的视图,而且没有那么慢。

1 个答案:

答案 0 :(得分:3)

  

有人可以在这里理解SQL Server的行为吗?对我来说   似乎有点像查询优化器的错误/失败。

不,这不是bug。

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

临时表方法无非就是将大型查询计划分成较小的部分并独立执行。

片段越小,SQL Server Query Optimizer不会在基数估计中执行一些戏剧性的不匹配,并选择正确的物理运算符和联接类型的机会就越大,因此,看到数百万行或数百万行的嵌套循环的机会就越小其他令人讨厌的事情。

如果需要运行下面所述的一段代码,查询优化器将知道每个涉及的临时表中有多少行以及它们如何分布:

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的跟踪。 因此,强制并行性也许偶尔会产生更好的计划,但是您几乎不能依靠它。

有关如何解决该问题的一些想法:

  • 有关表的统计信息更新
  • 玩转新的和传统的基数估计器
  • (imho)将CTE重写到派生表吗?
  • 如果涉及到大型数据集,则可以使用#temp表方法将逻辑拆分为较小的部分,这可能是一个一致的选择。
  • 等等等

有一个例外:

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