今天,工作中的主要DBA表示我不应该使用ITVF来完全包装视图,但是从我的基本基准测试来看,我对此持怀疑态度。看起来SQL Server只是在查询时对它实际需要的列(基于函数请求的内容)进行排序。我这样说是因为我看到下面两个例子之间的执行时间非常相似。
uf_GetCustomersByCity_A
在这个例子中,我创建了一个ITVF,它执行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
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使用优化)。在视图中提供所需的所有内容有很多价值,而不是在ITVF中专门指定每一列。
所以两者都工作得很好,在4-5秒内产生~500k行(注意:有很多复杂的条款可以延长执行时间,这些例子很难说明这里的目的)。 View有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的结构。我的预感在这里有效吗?
i
中的iTVF
用于内联 - 如您所知。这意味着,引擎将尝试找到最佳执行计划,就好像语句直接写入查询一样。
从这个角度来看,无论你使用是否应该没有区别
SELECT * FROM YourView WHERE idCity=@idCity
要么
SELECT * FROM YourITVF(@idCity)
引擎应该足够聪明,只能处理所需的列,但是 - 通常情况下 - 最好使用列的修复列表。 (参见@ a_horse_with_no_name评论中的链接。)
提示:当您使用SELECT * FROM ...
包装视图(如您所愿)时,您应该记住,如果您改变视图,则必须重新编译此iTVF。
问题可能是,引擎在解决深层嵌套结构方面存在问题,最终可能找不到最佳计划(甚至可能看不到,最终结果中不需要昂贵的计算列)。
如果您的视图是基于子视图构建的,并且这些子视图是从子视图,其他iTVF等构建的,那么这将导致次优计划。
几天前,我不得不调整一个慢视图,结果显示为具有9(!)调用级别的视图,覆盖视图中视图中的视图...以及大量计算列等等。引擎再也无法透过这个丛林了。
简而言之: