内联表值函数中 SELECT * 的性能
Posted
技术标签:
【中文标题】内联表值函数中 SELECT * 的性能【英文标题】:Performance of SELECT * in an Inline Table-Valued Function 【发布时间】:2019-03-01 06:46:15 【问题描述】:今天,工作中的首席 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 行(注意:有些复杂的子句会导致冗长的执行时间,这些示例很难说明这里的目的)。视图有大约 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 *
包装整个视图而不是一次指定一个列时,我根本看不出有什么不同,本质上重申了视图的结构。我的预感在这里有效吗?
【问题讨论】:
Why is SELECT * considered harmful 你为什么首先使用函数来包装你的视图?这有什么明显的好处吗? @ZoharPeled Demonstrable 有点牵强,但请参阅我的另一个半生不熟的问题,这至少在一定程度上解释了我的基本原理。基本上,我正在整合验证逻辑,我们在访问要在 Web 上显示给最终用户的视图时总是执行这些验证逻辑。由于我们最近在一些地方错过了验证,我认为用闪亮的新便利来吸引开发人员来包装这个现有的视图是明智的,并具有内在安全性的好处:security.stackexchange.com/questions/204344/… 考虑到 Shnugo 的回答,我想说这似乎是一件合理的事情,只要您在函数中运行实际验证。作为一个内联函数可以很容易地购买性能可能会使代码更麻烦...... 【参考方案1】:iTVF
中的i
用于内联 - 如您所知。这意味着,引擎将尝试找到最佳执行计划就像语句直接写入查询中一样。
从这个角度来看,你是否使用应该没有区别
SELECT * FROM YourView WHERE idCity=@idCity
或
SELECT * FROM YourITVF(@idCity)
引擎应该足够聪明,只处理需要的列,但是 - 一般而言 - 最好使用列的固定列表。 (请参阅@a_horse_with_no_name 评论中的链接。)
提示:当您使用 SELECT * FROM ...
包装视图(如您所愿)时,您应该记住,如果您更改视图,则必须重新编译此 iTVF。
问题可能是,引擎在解析深度嵌套的结构时遇到了麻烦,最终可能找不到最佳计划(甚至可能看不到最终结果中不需要昂贵的计算列)。
如果您的视图是基于子视图构建的,而这些子视图是从子子视图、其他 iTVF 等构建的,这将导致计划不理想。
几天前,我不得不调整一个 慢速视图,结果显示它是一个具有 9(!) 个调用级别的视图,覆盖了 ... 中的视图中的视图中的视图以及许多计算列等等。引擎无法再看穿这片丛林了。
简而言之:
尽量不要嵌套太深。 iTVF 可以产生更易读的代码(更少重复,说出名字) iTVF 可以带来更好的性能(因为它是使用一组固定参数预编译的,但要注意参数嗅探) 我不会使用 iTVF 来传递一个简单的过滤器变量...【讨论】:
说得好@shnugo Big +1 因为这就是极限。以上是关于内联表值函数中 SELECT * 的性能的主要内容,如果未能解决你的问题,请参考以下文章