SELECT * 在内联 Table 值函数中的性能

Performance of SELECT * in an Inline Table-Valued Function

今天,工作中的首席 DBA 说我不应该使用 ITVF 来完全包装视图,但从我的基本基准来看,我对此持怀疑态度。似乎 SQL 服务器只是在查询时整理出它实际需要的那些列(基于函数的请求)。我这么说是因为我发现下面两个示例的执行时间非常相似。


uf_GetCustomersByCity_A

在此示例中,我创建了一个执行 SELECT * 的 ITVF,返回过滤后的 CustomerView

CREATE FUNCTION [dbo].[uf_GetCustomersByCity_A] (@idCity INT)
RETURNS TABLE
AS RETURN

    SELECT CustView.*
      FROM [dbo].[CustomerView] CustView
     WHERE CustView.idCity = @idCity

GO

uf_GetCustomersByCity_B

CREATE FUNCTION [dbo].[uf_GetCustomersByCity_B] (@idCity INT)
RETURNS TABLE
AS RETURN

    SELECT CustView.idCustomer
         , CustView.cFullName
         , CustView.cCityName
         , CustView.fBalance
      FROM [dbo].[CustomerView] CustView
     WHERE CustView.idCity = @idCity

GO

我的问题是这是否是一个有效的观察结果,或者仅仅是调试许多小时的副作用(假设 SQL 服务器根据使用进行优化)。在视图中提供所需的所有内容而不是在 ITVF 中具体指定每一列有很多价值。

业余基准测试

所以两者都工作得很好,在 4-5 秒内产生了约 500k 行(注意:有一些复杂的子句借给了冗长的执行时间,这些例子很难说明这里的目的)。视图有大约 70 或 80 列,其中许多是内联格式化或操作的。

-- Around 500k rows in ~3-4 seconds:

SELECT idCustomer, cCityName
FROM [dbo].[uf_GetCustomersByCity_A](93)

-- Around 500k rows, again ~3-4 seconds:

SELECT idCustomer, cCityName
FROM [dbo].[uf_GetCustomersByCity_B](93)

在开发箱上的性能相同,但目前没有其他人在使用它。假设 cFullNamecGivenNamecFamilyName 的串联,而 cCityName 完全按照存储的方式返回。将 cCityName 添加到查询的影响明显低于 cFullName,这让我相信这不是我注意到的 SSMS 的交付时间。

-- Around 500k rows, ~6 seconds:

SELECT idCustomer, cFullName
FROM [dbo].[uf_GetCustomersByCity_A](93)

-- Around 500k rows, ~6 seconds:

SELECT idCustomer, cFullName
FROM [dbo].[uf_GetCustomersByCity_B](93)

我的想法是,如果 SELECT * 在 ITVF 中很重要,那么它将花费大量时间来确定它不使用的列的值。从我制定的快速基准测试来看,当我通过 SELECT * 包装整个视图而不是一次指定一个列时,我没有看到太大的区别,从本质上重申视图的结构。我的预感在这里有效吗?

iTVF 中的 i 用于 内联 - 如您所知。这意味着,引擎将尝试找到最佳执行计划,就像直接将语句写入查询中一样

从这个角度来看,你是否使用

应该没有区别
SELECT * FROM YourView WHERE idCity=@idCity

SELECT * FROM YourITVF(@idCity)

引擎应该足够聪明,可以只处理需要的列,但是 - 一般来说 - 最好使用固定的列列表。 (请参阅@a_horse_with_no_name 评论中的 link。)

提示:当你用SELECT * FROM ...包装一个视图(如你所愿)时你应该记住,如果你改变了你必须重新编译这个iTVF你的看法。

问题可能是,引擎无法解析深层嵌套结构,最终可能找不到最佳计划(甚至可能看不到最终结果不需要昂贵的计算列) .

如果您的视图是基于子视图构建的,而这些子视图是从子视图、其他 iTVF 等构建的,这将导致次优计划。

几天前,我不得不调整一个 慢速视图 结果是一个具有 9(!) 个调用级别的视图,涵盖视图中的视图中的视图中的视图...和很多计算列等等。引擎无法再看穿这片丛林了。

简而言之:

  • 尽量不要嵌套太深。
  • iTVF 可以使代码更易读(重复更少,说出名字
  • iTVF 可以带来更好的性能(因为它是使用一组固定参数预编译的,但要注意参数嗅探
  • 我不会使用 iTVF 只是为了传递一个简单的过滤器变量...