【问题标题】:Why do Scalar-valued functions in the "where" clause get slower?为什么“where”子句中的标量值函数变慢?
【发布时间】:2020-06-18 05:57:59
【问题描述】:

很遗憾,我无法准备一个示例以便您可以在计算机上重复它,但我认为这对于理解问题所在没有必要。 有这样的查询:

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 万条记录。如果我在“选择”条件下调用这个函数,那么这个函数会在 10 毫秒内执行,从磁盘读取的次数为 30。

如果您查看计划,您可以看到由于某种原因调度程序用“where”条件满足“with”条件,即他正在尝试优化组合条件的执行,我不需要它.我试图将分组添加到“with”条件或“distinct”条件没有帮助。

请帮助我了解问题所在?

【问题讨论】:

  • 您的评论帮助我提出了将函数移至“with”块的建议。这似乎是我的问题的解决方案。
  • 注意:你不应该使用nolock,除非你必须这样做,因为结果是不可预测的。

标签: sql-server tsql


【解决方案1】:

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 会创建两个独立的执行计划。

【讨论】:

    猜你喜欢
    • 2010-10-22
    • 1970-01-01
    • 2012-12-09
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 1970-01-01
    • 2021-08-26
    • 2021-05-02
    相关资源
    最近更新 更多