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)
在开发箱上的性能相同,但目前没有其他人在使用它。假设 cFullName
是 cGivenName
和 cFamilyName
的串联,而 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 只是为了传递一个简单的过滤器变量...
今天,工作中的首席 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)
在开发箱上的性能相同,但目前没有其他人在使用它。假设 cFullName
是 cGivenName
和 cFamilyName
的串联,而 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 只是为了传递一个简单的过滤器变量...