多语句表值函数与内联表值函数

问题描述 投票:178回答:8

举几个例子,只是说:

内联表值

CREATE FUNCTION MyNS.GetUnshippedOrders()
RETURNS TABLE
AS 
RETURN SELECT a.SaleId, a.CustomerID, b.Qty
    FROM Sales.Sales a INNER JOIN Sales.SaleDetail b
        ON a.SaleId = b.SaleId
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.ShipDate IS NULL
GO

多语句表值

CREATE FUNCTION MyNS.GetLastShipped(@CustomerID INT)
RETURNS @CustomerOrder TABLE
(SaleOrderID    INT         NOT NULL,
CustomerID      INT         NOT NULL,
OrderDate       DATETIME    NOT NULL,
OrderQty        INT         NOT NULL)
AS
BEGIN
    DECLARE @MaxDate DATETIME

    SELECT @MaxDate = MAX(OrderDate)
    FROM Sales.SalesOrderHeader
    WHERE CustomerID = @CustomerID

    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a INNER JOIN Sales.SalesOrderHeader b
        ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.OrderDate = @MaxDate
        AND a.CustomerID = @CustomerID
    RETURN
END
GO

使用一种类型(在线或多语句)优于另一种类型是否有优势?当一个人比另一个人好或者纯粹是语法上的差异时,是否存在某些情况?我意识到这两个示例查询正在做不同的事情,但我有理由以这种方式编写它们吗?

阅读它们以及优点/差异尚未得到解释。

sql sql-server sql-server-2008 tsql user-defined-functions
8个回答
131
投票

在研究马特的评论时,我修改了原来的陈述。他是正确的,即使它们都只是执行SELECT语句,内联表值函数(ITVF)和多语句表值函数(MSTVF)之间的性能也会有所不同。 SQL Server会将ITVF视为VIEW,因为它将使用有关表的最新统计信息计算执行计划。 MSTVF等效于将SELECT语句的全部内容填充到表变量中,然后加入到该变量中。因此,编译器不能对MSTVF中的表使用任何表统计信息。因此,在所有条件相同的情况下(他们很少),ITVF将比MSTVF表现更好。在我的测试中,完成时间的性能差异可以忽略不计,但从统计角度来看,这是显而易见的。

在您的情况下,这两个函数在功能上并不相同。 MSTV函数每次调用时都会执行额外的查询,最重要的是,会对客户ID进行过滤。在大型查询中,优化器将无法利用其他类型的连接,因为它需要为每个传递的customerId调用该函数。但是,如果您重新编写MSTV函数,如下所示:

CREATE FUNCTION MyNS.GetLastShipped()
RETURNS @CustomerOrder TABLE
    (
    SaleOrderID    INT         NOT NULL,
    CustomerID      INT         NOT NULL,
    OrderDate       DATETIME    NOT NULL,
    OrderQty        INT         NOT NULL
    )
AS
BEGIN
    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a 
        INNER JOIN Sales.SalesOrderHeader b
            ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c 
            ON b.ProductID = c.ProductID
    WHERE a.OrderDate = (
                        Select Max(SH1.OrderDate)
                        FROM Sales.SalesOrderHeader As SH1
                        WHERE SH1.CustomerID = A.CustomerId
                        )
    RETURN
END
GO

在查询中,优化器将能够调用该函数一次并构建更好的执行计划,但它仍然不会比等效的非参数化ITVS或VIEW更好。

在可行时,ITVF应优先于MSTVF,因为表中列的数据类型,可空性和排序规则,而您在多语句表值函数中声明这些属性,重要的是,您将从ITVF获得更好的执行计划。根据我的经验,我没有发现很多情况下ITVF比VIEW更好,但里程可能会有所不同。

感谢马特。

加成

自从我最近看到这个问题以来,Wayne Sheffield就是一个很好的分析,比较了内联表值函数和多语句函数之间的性能差异。

His original blog post.

Copy on SQL Server Central


26
投票

在内部,SQL Server将内联表值函数视为视图,并将多语句表值函数视为与存储过程类似。

当内联表值函数用作外部查询的一部分时,查询处理器将扩展UDF定义并使用这些对象上的索引生成访问基础对象的执行计划。

对于多语句表值函数,为函数本身创建执行计划并将其存储在执行计划缓存中(一旦第一次执行该函数)。如果多语句表值函数被用作较大查询的一部分,则优化器不知道函数返回什么,因此做出一些标准假设 - 实际上它假设函数将返回单行,并且返回通过对具有单行的表使用表扫描来访问该函数。

多语句表值函数表现不佳的情况是,它们返回大量行并在外部查询中连接。性能问题主要取决于优化器将生成一个假定返回单行的计划,这不一定是最合适的计划。

作为一般经验法则,我们发现,由于这些潜在的性能问题,应该使用内联表值函数而不是多语句函数(当UDF将用作外部查询的一部分时)。


13
投票

还有另一个不同之处。可以插入,更新和删除内联表值函数 - 就像视图一样。类似的限制适用 - 无法使用聚合更新函数,无法更新计算列等。


3
投票

我认为,你的例子很好地回答了这个问题。第一个函数可以作为单个选择完成,并且是使用内联样式的一个很好的理由。第二个可能是作为单个语句完成的(使用子查询来获取最大日期),但是一些编码器可能会发现它更容易阅读或更自然地在多个语句中执行它。有些函数只是在一个语句中无法完成,因此需要多语句版本。

我建议尽可能使用最简单的(内联),并在必要时(显然)使用多语句,或者当个人偏好/可读性使得wirth成为额外的输入时。


0
投票

看看Comparing Inline and Multi-Statement Table-Valued Functions你可以找到很好的描述和性能基准


0
投票

我没有测试过这个,但是多语句函数会缓存结果集。可能存在优化器内联函数过多的情况。例如,假设您有一个函数可以从不同的数据库返回结果,具体取决于您作为“公司编号”传递的内容。通常情况下,您可以创建一个带有union的视图,然后按公司编号进行筛选,但我发现有时sql server会撤回整个union,并且不够聪明,无法调用一个select。表函数可以具有选择源的逻辑。


0
投票

使用多行函数的另一种情况是绕过sql server来推下where子句。

例如,我有一个带有表名的表,一些表名的格式如C05_2019和C12_2018,并且所有以这种方式格式化的表具有相同的模式。我想将所有数据合并到一个表中,并将05和12解析为CompNo列,将2018,2019解析为年份列。但是,还有其他表,如ACA_StupidTable,我无法提取CompNo和CompYr,如果我尝试,会得到转换错误。所以,我的查询分为两部分,一个内部查询只返回格式为'C_______'的表,然后外部查询执行子字符串和int转换。即将Cast(子串(2,2)作为int)作为CompNo。所有看起来都很好,除了sql server决定在结果被过滤之前放入我的Cast函数,所以我得到一个思想加扰转换错误。多语句表函数可以防止这种情况发生,因为它基本上是一个“新”表。


-2
投票

如果您要进行查询,可以加入您的内联表值函数,例如:

SELECT
    a.*,b.*
    FROM AAAA a
        INNER JOIN MyNS.GetUnshippedOrders() b ON a.z=b.z

它会产生很少的开销并且运行良好。

如果您尝试在类似查询中使用多语句表值,则会出现性能问题:

SELECT
    x.a,x.b,x.c,(SELECT OrderQty FROM MyNS.GetLastShipped(x.CustomerID)) AS Qty
    FROM xxxx   x

因为您将为返回的每一行执行1次函数,因为结果集变大,它将运行得越来越慢。

© www.soinside.com 2019 - 2024. All rights reserved.