SQL Server Profiler 与 COALESCE(SP:STMTCOMPLETED)
SQL Server Profiler vs COALESCE (SP:STMTCOMPLETED)
我是运行一个踪迹,发现了一些我不明白的东西。我有一个带有简单 select 语句的过程,该语句 returns 来自一系列函数调用的第一个非空值。
我希望每个函数只被调用一次。因此,在分析 SP:STMTCOMPLETED 事件时,我希望只看到一组记录,其中包含来自该调用的语句。
但是,我看到的是那组记录多次,这表明该函数被多次调用。
函数真的被调用了两次吗?为什么?
这是创建函数和存储过程的脚本:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[FOO] (@v INT)
RETURNS bit AS
BEGIN
RETURN 0
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[POO]
@v int,
@p varchar(100)
AS
SELECT
@p as p,
CASE @p
WHEN 'x' THEN COALESCE(dbo.FOO(@v), 0)
ELSE 1
END as poo
GO
这是程序调用
exec dbo.POO @v = 13911, @p = 'x'
这里是 Profiler 输出,显示两行 FOO 函数和一行 POO 过程:
如果我用这样的 CASE 语句替换 COALESCE 调用:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[POO]
@v int,
@p varchar(100)
AS
SELECT
@p as p,
CASE @p
WHEN 'x' THEN CASE WHEN dbo.FOO(@v) = 1 THEN 1 ELSE 0 END
ELSE 1
END as poo
GO
我在探查器上只得到一行 FOO:
这是由 SQL Profiler 生成的分析脚本(按数据库名称 = dbTest 过滤):
/****************************************************/
/* Created by: SQL Server 2017 Profiler */
/* Date: 05/08/2020 12:06:04 AM */
/****************************************************/
-- Create a Queue
declare @rc int
declare @TraceID int
declare @maxfilesize bigint
set @maxfilesize = 5
-- Please replace the text InsertFileNameHere, with an appropriate
-- filename prefixed by a path, e.g., c:\MyFolder\MyTrace. The .trc extension
-- will be appended to the filename automatically. If you are writing from
-- remote server to local drive, please use UNC path and make sure server has
-- write access to your network share
exec @rc = sp_trace_create @TraceID output, 0, N'InsertFileNameHere', @maxfilesize, NULL
if (@rc != 0) goto error
-- Client side File and Table cannot be scripted
-- Set the events
declare @on bit
set @on = 1
exec sp_trace_setevent @TraceID, 45, 1, @on
exec sp_trace_setevent @TraceID, 45, 9, @on
exec sp_trace_setevent @TraceID, 45, 3, @on
exec sp_trace_setevent @TraceID, 45, 4, @on
exec sp_trace_setevent @TraceID, 45, 5, @on
exec sp_trace_setevent @TraceID, 45, 6, @on
exec sp_trace_setevent @TraceID, 45, 7, @on
exec sp_trace_setevent @TraceID, 45, 8, @on
exec sp_trace_setevent @TraceID, 45, 10, @on
exec sp_trace_setevent @TraceID, 45, 11, @on
exec sp_trace_setevent @TraceID, 45, 12, @on
exec sp_trace_setevent @TraceID, 45, 13, @on
exec sp_trace_setevent @TraceID, 45, 14, @on
exec sp_trace_setevent @TraceID, 45, 15, @on
exec sp_trace_setevent @TraceID, 45, 16, @on
exec sp_trace_setevent @TraceID, 45, 17, @on
exec sp_trace_setevent @TraceID, 45, 18, @on
exec sp_trace_setevent @TraceID, 45, 22, @on
exec sp_trace_setevent @TraceID, 45, 25, @on
exec sp_trace_setevent @TraceID, 45, 26, @on
exec sp_trace_setevent @TraceID, 45, 28, @on
exec sp_trace_setevent @TraceID, 45, 29, @on
exec sp_trace_setevent @TraceID, 45, 34, @on
exec sp_trace_setevent @TraceID, 45, 35, @on
exec sp_trace_setevent @TraceID, 45, 41, @on
exec sp_trace_setevent @TraceID, 45, 48, @on
exec sp_trace_setevent @TraceID, 45, 49, @on
exec sp_trace_setevent @TraceID, 45, 50, @on
exec sp_trace_setevent @TraceID, 45, 51, @on
exec sp_trace_setevent @TraceID, 45, 55, @on
exec sp_trace_setevent @TraceID, 45, 60, @on
exec sp_trace_setevent @TraceID, 45, 61, @on
exec sp_trace_setevent @TraceID, 45, 62, @on
exec sp_trace_setevent @TraceID, 45, 64, @on
exec sp_trace_setevent @TraceID, 45, 66, @on
-- Set the Filters
declare @intfilter int
declare @bigintfilter bigint
exec sp_trace_setfilter @TraceID, 35, 0, 6, N'dbTest'
-- Set the trace status to start
exec sp_trace_setstatus @TraceID, 1
-- display trace id for future references
select TraceID=@TraceID
goto finish
error:
select ErrorCode=@rc
finish:
go
是的,它被调用了两次。
扩展为 CASE WHEN dbo.FOO(@v) IS NOT NULL THEN dbo.FOO(@v) ELSE 0 END
对 dbo.FOO
的两个引用分别进行评估。
您可以使用 ISNULL(dbo.FOO(@v), 0)
来避免这种情况。
有关此内容的更多信息,请参阅 Unnecessarily bad performance for coalesce(subquery)(从旧的 Connect 站点迁移到新的反馈站点)
顺便说一句,在 2019 年使用标量 UDF 内联时,它根本不会在运行时调用该函数——在这个简单的示例中,整个事情被简化为 CASE WHEN [@p]='x' THEN (0) ELSE (1) END
(因为它总是看到函数 returns 0
并将该值替换为).
我是运行一个踪迹,发现了一些我不明白的东西。我有一个带有简单 select 语句的过程,该语句 returns 来自一系列函数调用的第一个非空值。
我希望每个函数只被调用一次。因此,在分析 SP:STMTCOMPLETED 事件时,我希望只看到一组记录,其中包含来自该调用的语句。
但是,我看到的是那组记录多次,这表明该函数被多次调用。
函数真的被调用了两次吗?为什么?
这是创建函数和存储过程的脚本:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[FOO] (@v INT)
RETURNS bit AS
BEGIN
RETURN 0
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[POO]
@v int,
@p varchar(100)
AS
SELECT
@p as p,
CASE @p
WHEN 'x' THEN COALESCE(dbo.FOO(@v), 0)
ELSE 1
END as poo
GO
这是程序调用
exec dbo.POO @v = 13911, @p = 'x'
这里是 Profiler 输出,显示两行 FOO 函数和一行 POO 过程:
如果我用这样的 CASE 语句替换 COALESCE 调用:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[POO]
@v int,
@p varchar(100)
AS
SELECT
@p as p,
CASE @p
WHEN 'x' THEN CASE WHEN dbo.FOO(@v) = 1 THEN 1 ELSE 0 END
ELSE 1
END as poo
GO
我在探查器上只得到一行 FOO:
这是由 SQL Profiler 生成的分析脚本(按数据库名称 = dbTest 过滤):
/****************************************************/
/* Created by: SQL Server 2017 Profiler */
/* Date: 05/08/2020 12:06:04 AM */
/****************************************************/
-- Create a Queue
declare @rc int
declare @TraceID int
declare @maxfilesize bigint
set @maxfilesize = 5
-- Please replace the text InsertFileNameHere, with an appropriate
-- filename prefixed by a path, e.g., c:\MyFolder\MyTrace. The .trc extension
-- will be appended to the filename automatically. If you are writing from
-- remote server to local drive, please use UNC path and make sure server has
-- write access to your network share
exec @rc = sp_trace_create @TraceID output, 0, N'InsertFileNameHere', @maxfilesize, NULL
if (@rc != 0) goto error
-- Client side File and Table cannot be scripted
-- Set the events
declare @on bit
set @on = 1
exec sp_trace_setevent @TraceID, 45, 1, @on
exec sp_trace_setevent @TraceID, 45, 9, @on
exec sp_trace_setevent @TraceID, 45, 3, @on
exec sp_trace_setevent @TraceID, 45, 4, @on
exec sp_trace_setevent @TraceID, 45, 5, @on
exec sp_trace_setevent @TraceID, 45, 6, @on
exec sp_trace_setevent @TraceID, 45, 7, @on
exec sp_trace_setevent @TraceID, 45, 8, @on
exec sp_trace_setevent @TraceID, 45, 10, @on
exec sp_trace_setevent @TraceID, 45, 11, @on
exec sp_trace_setevent @TraceID, 45, 12, @on
exec sp_trace_setevent @TraceID, 45, 13, @on
exec sp_trace_setevent @TraceID, 45, 14, @on
exec sp_trace_setevent @TraceID, 45, 15, @on
exec sp_trace_setevent @TraceID, 45, 16, @on
exec sp_trace_setevent @TraceID, 45, 17, @on
exec sp_trace_setevent @TraceID, 45, 18, @on
exec sp_trace_setevent @TraceID, 45, 22, @on
exec sp_trace_setevent @TraceID, 45, 25, @on
exec sp_trace_setevent @TraceID, 45, 26, @on
exec sp_trace_setevent @TraceID, 45, 28, @on
exec sp_trace_setevent @TraceID, 45, 29, @on
exec sp_trace_setevent @TraceID, 45, 34, @on
exec sp_trace_setevent @TraceID, 45, 35, @on
exec sp_trace_setevent @TraceID, 45, 41, @on
exec sp_trace_setevent @TraceID, 45, 48, @on
exec sp_trace_setevent @TraceID, 45, 49, @on
exec sp_trace_setevent @TraceID, 45, 50, @on
exec sp_trace_setevent @TraceID, 45, 51, @on
exec sp_trace_setevent @TraceID, 45, 55, @on
exec sp_trace_setevent @TraceID, 45, 60, @on
exec sp_trace_setevent @TraceID, 45, 61, @on
exec sp_trace_setevent @TraceID, 45, 62, @on
exec sp_trace_setevent @TraceID, 45, 64, @on
exec sp_trace_setevent @TraceID, 45, 66, @on
-- Set the Filters
declare @intfilter int
declare @bigintfilter bigint
exec sp_trace_setfilter @TraceID, 35, 0, 6, N'dbTest'
-- Set the trace status to start
exec sp_trace_setstatus @TraceID, 1
-- display trace id for future references
select TraceID=@TraceID
goto finish
error:
select ErrorCode=@rc
finish:
go
是的,它被调用了两次。
扩展为 CASE WHEN dbo.FOO(@v) IS NOT NULL THEN dbo.FOO(@v) ELSE 0 END
对 dbo.FOO
的两个引用分别进行评估。
您可以使用 ISNULL(dbo.FOO(@v), 0)
来避免这种情况。
有关此内容的更多信息,请参阅 Unnecessarily bad performance for coalesce(subquery)(从旧的 Connect 站点迁移到新的反馈站点)
顺便说一句,在 2019 年使用标量 UDF 内联时,它根本不会在运行时调用该函数——在这个简单的示例中,整个事情被简化为 CASE WHEN [@p]='x' THEN (0) ELSE (1) END
(因为它总是看到函数 returns 0
并将该值替换为).