为什么 "where "子句中的标量值函数会变慢?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么 "where "子句中的标量值函数会变慢?相关的知识,希望对你有一定的参考价值。

遗憾的是,我不能准备一个例子让你在电脑上重复,但我认为这不是了解问题所在的必要条件.有这样的疑问。

with cte as (
select  selectStations.ReceiverUID, 1907 as id
    from WF4_Routes r WITH (NOLOCK) 
    inner join WF4_Stages selectStages WITH (NOLOCK) on selectStages.RouteID = r.ID and r.SentToCAS = 1 and r.PRUZ <> 1 and selectStages.PRUZ <> 1 and selectStages.StageType = 1 
    inner join WF4_Stations selectStations WITH (NOLOCK) on selectStations.ApprovalStageID = selectStages.ID and selectStations.PRUZ <> 1
)
select *--case when dbo.fnGetGkExchangeParticipantQuick(cte.ReceiverUID, id)  = 'E477B8FA-7539-4B43-8961-807A29FECFC0' then 1 else 0 end
from cte
where dbo.fnGetGkExchangeParticipantQuick(cte.ReceiverUID, id) = 'E477B8FA-7539-4B43-8961-807A29FECFC0'

从整个脚本来看,在 "where "条件下调用标量函数的时刻 和在 "select "中注释出来的调用是很重要的。

CREATE FUNCTION [dbo].fnGetGkExchangeParticipantQuick(@CurrentUID uniqueidentifier, @IsExchangeParticipantAttrID int) 
RETURNS uniqueidentifier 
AS  
BEGIN 
  declare @UnitUID uniqueidentifier 

  select @UnitUID = UID from GL_OBJECTS where UID = @CurrentUID and ACTIVE = 1

  while @UnitUID is not null
  begin
    if (select top 1 cast(PropertyValue as bit) from MB_ATTRIBUTES_TO_VALUES where Object_UID = @UnitUID and Attribute_ID = @IsExchangeParticipantAttrID) = 1 
        break;

    set @UnitUID = null
    select @UnitUID = PARENT from GL_OBJECTS where UID = @UnitUID and ACTIVE = 1
  end

  return @UnitUID
END
GO

该函数从对象的层次结构中的父对象中搜索一个特定的属性。

如果我在 "where "条件下调用这个函数,那么查询将在1秒内执行,而从磁盘中读取的记录将达到60万条左右。如果我在 "select "条件下调用这个函数,那么这个函数将在10毫秒内执行,从磁盘上读取的数量为30条。

如果你看一下计划,你可以看到,由于某种原因,调度器用 "where "条件满足了 "with "条件,也就是说,他是想优化组合条件的执行,我不需要。我试着在 "with "条件或 "different "条件中加入分组,都没有用。

请帮我理解问题出在哪里?

答案

cte往往不过是语法上的糖,SQL Server将整个查询组合成一个它认为最好的执行计划。

有时你可以通过将CTE中的函数调用添加为一列,然后在你的 where 条款。

with cte as (
    select selectStations.ReceiverUID
      , 1907 as id
      , dbo.fnGetGkExchangeParticipantQuick(selectStations.ReceiverUID, 1907) as ExchangeParticipant
    from WF4_Routes r WITH (NOLOCK) 
    inner join WF4_Stages selectStages WITH (NOLOCK) on selectStages.RouteID = r.ID and r.SentToCAS = 1 and r.PRUZ <> 1 and selectStages.PRUZ <> 1 and selectStages.StageType = 1 
    inner join WF4_Stations selectStations WITH (NOLOCK) on selectStations.ApprovalStageID = selectStages.ID and selectStations.PRUZ <> 1
)
select *
from cte
where ExchangeParticipant = 'E477B8FA-7539-4B43-8961-807A29FECFC0';

注意事项。

您永远不应该在 where 子句,因为不能使用索引,而且经常需要进行全表扫描。

而如果需要强制先执行查询的CTE部分,那么可以将结果具体化到一个临时表中,然后从中选择。这样SQL Server就创建了两个独立的执行计划。

以上是关于为什么 "where "子句中的标量值函数会变慢?的主要内容,如果未能解决你的问题,请参考以下文章

如何在使用 Linq 的 Where 子句之后选择数组索引?

这个 sql where 子句中的“&”是啥意思?

为啥我在 View SELECT 查询中收到带有 WHERE 子句的“未知列 'xyz'”?

使用 wpdb prepare 安全地收集带有“join”的“where”子句数组

在“where”子句中使用“in”运算符更新行

在子查询中续集“WHERE”子句