今天,在工作的首席DBA表示我不应该使用ITVF完全包装视图,但是从我的基本基准来看,我持怀疑态度。看起来SQL Server只是在查询时整理出它实际需要的那些列(基于该函数的请求)。我之所以这样说,是因为我看到下面两个示例之间的执行时间非常相似。
uf_GetCustomersByCity_A
在此示例中,我创建了一个执行SELECT *
并返回过滤后的CustomerView
的ITVF。
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 Server在使用时进行了优化)。在View中提供所需的所有内容,而不是在ITVF中专门指定每一列,具有很大的价值。
因此,两者都运行良好,在4-5秒钟内可产生约50万行(注意:有些复杂的子句会延长执行时间,这些示例在这里很难说明目的)。该视图有大约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 *
包装整个View而不是一次指定一个列,根本没有太大的区别,本质上是重述了View的结构。我的直觉在这里有效吗?
答案 0 :(得分:2)
i
中的iTVF
用于内联-如您所知。这意味着,引擎将尝试找到最佳执行计划,就像直接将语句写入查询。
从这个角度来看,无论您是否使用
SELECT * FROM YourView WHERE idCity=@idCity
或
SELECT * FROM YourITVF(@idCity)
引擎应该足够聪明,只处理所需的列,但是-通常-最好使用列的固定列表。 (请参阅@a_horse_with_no_name注释中的链接。)
提示:使用SELECT * FROM ...
包装视图(根据需要)时,请记住,如果更改视图,则必须重新编译该iTVF。>
问题可能是引擎难以解析深层嵌套的结构,最终可能找不到最佳计划(甚至可能看不到,最终结果中不需要昂贵的计算列)。 / p>
如果您的视图基于子视图,而这些子视图是基于子子视图,其他iTVF等构建的,则将导致次优计划。
几天前,我不得不调整慢速视图,结果显示为具有9(!)调用级别的视图,涵盖了...中视图的视图以及许多计算列等等。引擎无法再浏览这个丛林了。
简而言之: