内联表值函数中 SELECT * 的性能

AJA*_*JAr 0 t-sql sql-server stored-functions set-returning-functions

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


uf_GetCustomersByCity_A

在此示例中,我创建了一个 ITVF,它执行 a 操作SELECT *,返回一个已过滤的CustomerView

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

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

GO
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我的问题是,这是否是一个有效的观察结果,或者只是长时间调试的副作用(假设 SQL Server 通过使用进行优化)。在视图中提供所需的所有内容而不是在 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)

Run Code Online (Sandbox Code Playgroud)

开发盒上的性能相同,但目前没有其他人使用它。假设这是和cFullName的串联,而返回的结果与存储时完全相同。添加到查询的影响明显低于,这让我相信我注意到的不是 SSMS 的交付时间。cGivenNamecFamilyNamecCityNamecCityNamecFullName

-- 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)

Run Code Online (Sandbox Code Playgroud)

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

Shn*_*ugo 5

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

从这个角度来看,无论使用还是使用应该没有什么区别

SELECT * FROM YourView WHERE idCity=@idCity
Run Code Online (Sandbox Code Playgroud)

或者

SELECT * FROM YourITVF(@idCity)
Run Code Online (Sandbox Code Playgroud)

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

提示:当您包装视图(如您想要的那样)时,SELECT * FROM ...您应该记住,如果您更改视图,则必须重新编译此 iTVF。

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

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

几天前,我不得不调整一个缓慢的视图,结果是一个具有 9(!) 个调用级别的视图,覆盖了视图中的视图中的视图......以及许多计算列等等。引擎再也无法穿过这片丛林了。

简而言之:

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