这个问题更像是一个假设而不是实际的代码问题。但我提供了一个愚蠢的代码版本来说明问题。请不要评论代码本身的愚蠢。实际的代码太复杂(和专有),所以这是最好的方法。
我有一个标量值函数如下。
CREATE FUNCTION [dbo].[Compute_value]
(
@alpha FLOAT,
@bravo FLOAT,
@charle FLOAT,
@delta FLOAT
)
RETURNS FLOAT
AS
BEGIN
IF @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0
RETURN 0
IF @bravo IS NULL OR @bravo <= 0
RETURN 100
IF (@charle + @delta) / @bravo <= 0
RETURN 100
DECLARE @x = DATEDIFF(GETDATE(),'1/1/2000')
RETURN @alpha * POWER((100 / @delta), (-2 * POWER(@charle * @bravo, @x/365)))
END
我听说表值函数的运行速度通常比标量值函数快得多,因为它们不是RBAR。所以我转换逻辑使用#temp_table构造只是为了对它进行基准测试。我将获得与#temp_table相同数量的UPDATE,而不是十几个IF语句,它比标量UDF慢了两倍。
我想也许这种情况正在发生,因为UDF可以在前几个条件下快速返回,从而导致大部分标量UDF为无操作,但实际情况并非如此。检查#temp_table解决方案的查询执行计划似乎表明更新导致了大部分计划成本。
我在这里可能会缺少什么?如果我将它转换为表值函数,我是否坚持对每个条件语句的整个表变量进行更新?有没有办法避免这种情况,这似乎会大大减缓事情的发展?我错过了一些明显的东西吗?
答案 0 :(得分:3)
此处的关键字字词为 INLINE TABLE VALUED FUNCTIONS 。您有两种类型的T-SQL表值函数:多语句和内联。如果你的T-SQL函数以BEGIN语句开头,那么它将成为废话 - 标量或其他。您无法将临时表转换为内联表值函数,因此我假设您从标量转到mutli语句表值函数,这可能会更糟。
您的内联表值函数(iTVF)应如下所示:
CREATE FUNCTION [dbo].[Compute_value]
(
@alpha FLOAT,
@bravo FLOAT,
@charle FLOAT,
@delta FLOAT
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newValue =
CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
ELSE @alpha * POWER((100 / @delta),
(-2 * POWER(@charle * @bravo, DATEDIFF(<unit of measurement>,GETDATE(),'1/1/2000')/365)))
END
GO;
请注意,在您发布的代码中,DATEDIFF
语句缺少datepart
参数。如果应该看起来像:
@x int = DATEDIFF(DAY, GETDATE(),'1/1/2000')
更进一步 - 理解为什么iTVF比T-SQL标量值用户定义函数更好是很重要的。这不是因为表值函数比标量值函数更快,因为Microsoft的T-SQL内联函数的实现比它们不是内联的T-SQL函数的实现更快。请注意以下三个执行相同操作的功能:
-- Scalar version
CREATE FUNCTION dbo.Compute_value_scalar
(
@alpha FLOAT,
@bravo FLOAT,
@charle FLOAT,
@delta FLOAT
)
RETURNS FLOAT
AS
BEGIN
IF @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0
RETURN 0
IF @bravo IS NULL OR @bravo <= 0
RETURN 100
IF (@charle + @delta) / @bravo <= 0
RETURN 100
DECLARE @x int = DATEDIFF(dd, GETDATE(),'1/1/2000')
RETURN @alpha * POWER((100 / @delta), (-2 * POWER(@charle * @bravo, @x/365)))
END
GO
-- multi-statement table valued function
CREATE FUNCTION dbo.Compute_value_mtvf
(
@alpha FLOAT,
@bravo FLOAT,
@charle FLOAT,
@delta FLOAT
)
RETURNS @sometable TABLE (newValue float) AS
BEGIN
INSERT @sometable VALUES
(
CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
ELSE @alpha * POWER((100 / @delta),
(-2 * POWER(@charle * @bravo, DATEDIFF(DAY,GETDATE(),'1/1/2000')/365)))
END
)
RETURN;
END
GO
-- INLINE table valued function
CREATE FUNCTION dbo.Compute_value_itvf
(
@alpha FLOAT,
@bravo FLOAT,
@charle FLOAT,
@delta FLOAT
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newValue =
CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
ELSE @alpha * POWER((100 / @delta),
(-2 * POWER(@charle * @bravo, DATEDIFF(DAY,GETDATE(),'1/1/2000')/365)))
END
GO
现在进行一些样本数据和性能测试:
SET NOCOUNT ON;
CREATE TABLE #someTable (alpha FLOAT, bravo FLOAT, charle FLOAT, delta FLOAT);
INSERT #someTable
SELECT TOP (100000)
abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1,
abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1
FROM sys.all_columns a, sys.all_columns b;
PRINT char(10)+char(13)+'scalar'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;
SELECT @z = dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
FROM #someTable t;
PRINT DATEDIFF(ms, @st, getdate());
GO
PRINT char(10)+char(13)+'mtvf'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;
SELECT @z = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_mtvf(t.alpha, t.bravo, t.charle, t.delta) f;
PRINT DATEDIFF(ms, @st, getdate());
GO
PRINT char(10)+char(13)+'itvf'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;
SELECT @z = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_itvf(t.alpha, t.bravo, t.charle, t.delta) f;
PRINT DATEDIFF(ms, @st, getdate());
GO
结果:
scalar
------------------------------------------------------------
2786
mTVF
------------------------------------------------------------
41536
iTVF
------------------------------------------------------------
153
标量udf运行2.7秒,mtvf运行41秒,iTVF运行0.153秒。要理解为什么让我们看看估计的执行计划:
当您查看实际的执行计划时,您看不到这一点,但是,使用标量udf和mtvf,优化器会为每一行调用一些执行不佳的子例程; iTVF没有。引用Paul White's career changing article about APPLY保罗写道:
您可能会发现将iTVF视为接受的视图很有用 参数。就像视图一样,SQL Server扩展了一个的定义 iTVF直接进入封闭查询的查询计划之前 执行优化。
效果是SQL Server能够应用其全部范围 优化,考虑整个查询。就像你一样 已经手工编写了扩展的查询....
换句话说,iTVF使优化器能够以所有其他代码都需要执行时不可能的方式优化查询。 iTVF优势的众多其他例子之一是它们是上述三种允许并行性的函数类型中唯一的一种。让我们再次运行每个函数,这次启用实际执行计划并使用traceflag 8649(强制执行并行执行计划):
-- don't need so many rows for this test
TRUNCATE TABLE #sometable;
INSERT #someTable
SELECT TOP (10)
abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1,
abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1
FROM sys.all_columns a;
DECLARE @x float;
SELECT TOP (10) @x = dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
FROM #someTable t
ORDER BY dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
OPTION (QUERYTRACEON 8649);
SELECT TOP (10) @x = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_mtvf(t.alpha, t.bravo, t.charle, t.delta) f
ORDER BY f.newValue
OPTION (QUERYTRACEON 8649);
SELECT @x = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_itvf(t.alpha, t.bravo, t.charle, t.delta) f
ORDER BY f.newValue
OPTION (QUERYTRACEON 8649);
执行计划:
您为iTVF的执行计划看到的那些箭头是并行性的 - 您的所有CPU(或您的SQL实例MAXDOP
设置允许的数量)允许一起工作。 T-SQL标量和mtvf UDF无法做到这一点。当Microsoft引入内联标量UDF时,我建议那些你正在做的事情,但是在那之前:如果性能是你正在寻找的,那么内联是唯一的方法,对于那个,iTVF是镇上唯一的游戏。
请注意,在谈论函数时,我一直强调 T-SQL ... CLR Scalar和Table值函数可以很好但是这是一个不同的主题