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


198

举例说明,以防万一:

内联表值

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

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

关于它们及其优势/差异的阅读并没有真正得到解释。


内联函数的另一个巨大好处是,您可以选择ROWID(TIMESTAMP)列,而不能在多语句函数中将TIMESTAMP数据插入返回表中!
Artru 2013年

3
感谢您的出色线索。我学到了很多东西。但是,要记住的一件事是,当将ITV的功能更改为MSTV时,分析器认为您正在更改ITV。无论您从MSTV的角度如何正确获取语法,重新编译总是会失败,通常是在BEGIN之后的第一个语句周围。解决此问题的唯一方法是删除旧功能,然后将新功能创建为MSTV。
Fandango68

Answers:


141

在研究Matt的评论时,我修改了原始声明。他是正确的,即使直接执行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或a更好VIEW

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

感谢Matt。

加成

自从我最近看到这一点以来,这是Wayne Sheffield进行的出色分析,比较了内联表值函数和多语句函数之间的性能差异。

他的原始博客文章。

在SQL Server Central上复制


40
事实并非如此-多语句功能通常会严重打击性能,因为它们会阻止查询优化程序使用统计信息。如果我每次看到多语句函数使用都会导致执行计划的选择很差(我通常每次将返回的行数估计为1)时就只有1美元,那我就足够买一辆小型车了。
马特·惠特菲尔德

我找到的最好的解释是在第一个答案中,以及相关的文章:stackoverflow.com/questions/4109152/…不要错过相关的文档,您可以快速阅读它,这非常有趣。
JotaBe 2015年

1
对于SQL Server 2017,此答案是否有更新?:youtube.com/watch
拉尔夫(Ralph)

29

在内部,SQL Server像对待视图一样对待内联表值函数,并像对待存储过程一样对待多语句表值函数。

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

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

多语句表值函数执行不佳的地方是当它们返回大量行并在外部查询中联接时。性能问题主要归结于这样一个事实,即优化器将假设返回一个行而生成一个计划,这不一定是最合适的计划。

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


2
尽管它可以像对待存储过程一样对待多语句表值函数,但是在功能上相同的存储过程要比大型数据集的表值函数快得多。我坚持使用多语句表值函数存储的proc。
Kekoa

6
除非您需要将这些结果加入另一个查询中。
GuillermoGutiérrez13年

为什么不同时使用两者?一个存储的proc,它返回多语句表值函数的结果。两全其美。
罗宾诺

13

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


3

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

我建议尽可能使用最简单的(内联)语句,并在必要时(显然)或在个人喜好/可读性使其成为多余打字内容时使用多语句。


感谢你的回答。因此,从根本上来说,出于可读性考虑,仅在函数比内联函数中可行时复杂时才真正使用多语句吗?多语句对性能完全没有好处吗?
AndrewC 2010年

我不知道,但我不这么认为。最好让sql server找出您可以尝试手动进行的优化(通过使用变量,临时表或其他方法)。尽管您当然可以做一些性能测试来证明/证明在特定情况下的性能。

再次非常感谢。当我有更多时间时,我可能会进一步研究!:)
AndrewC


0

我还没有测试过,但是多语句功能会缓存结果集。在某些情况下,优化器无法内联函数。例如,假设您有一个函数,该函数根据您作为“公司编号”传递的内容从不同的数据库返回结果。通常,您可以创建一个带有联合的视图,然后按公司编号过滤,但我发现有时sql server会拉回整个联合,并且不够智能,无法调用单选。表函数可以具有选择源的逻辑。


0

使用多行功能的另一种情况是避免sql server下推where子句。

例如,我有一个带有表名的表,并且某些表名的格式如C05_2019和C12_2018,并且以这种方式格式化的所有表都具有相同的架构。我想将所有数据合并到一个表中,并将05和12解析为CompNo列,将2018,2019解析为year列。但是,还有其他表,例如ACA_StupidTable,我无法提取CompNo和CompYr,如果尝试会得到转换错误。因此,我的查询分为两部分,一个内部查询仅返回格式为“ C_______”的表,然后外部查询进行了子字符串和int转换。即Cast(Substring(2,2)int)为CompNo。除了SQL Server决定在过滤结果之前放置Cast函数外,其他所有内容看起来都很不错,所以我想到了一个令人难以置信的转换错误。多语句表功能可能会阻止这种情况的发生,


0

也许以一种非常简洁的方式。ITVF(内联TVF):如果您是DB人,则更多,是一种参数化视图,只需一个SELECT键

MTVF(多语句TVF):开发人员,创建并加载表变量。


-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次,结果集变大,它将运行的越来越慢。


嗯,所以您会说内联在性能方面要好得多?
AndrewC

1
不,它们都返回一个表,这将使您的第二个SQL无效,因为您试图将表放入列中。
cjk 2010年

1
@ck,我更新了您评论过的查询。第二个函数中使用的函数的参数将其用作子查询,这将导致性能下降。
KM。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.