问题描述
背景
大家好!
我最近了解到,在较新版本的 sql Server 中,查询优化器可以“扩展”sql 视图并利用内联性能优势。这可能会对我创建哪些类型的数据库对象以及创建它们的原因和时间产生一些重大影响,具体取决于何时实现这种增强的性能,何时不实现。
例如,我不会费心为一个非常大的事务表(其中性能非常重要)创建一个带有开始日期参数和结束日期参数的参数化内联表值函数,当我可以查看并拍打时WHERE
语句位于调用查询的底部,类似于
SELECT
Column1
FROM vw_Simple
WHERE
Column1 BETWEEN @SomeStartDate AND @SomeEndDate
并相信查询优化器会“扩展”视图并为我提供出色的执行计划。
注意:我说的是一个简单的、非嵌套的、非索引的 sql Server 视图。类似的东西
CREATE VIEW vw_Simple
AS
SELECT
Column1,Column2
FROM TableA
问题
我的问题是:了解查询优化器何时可以“扩展”sql 视图以及何时不能“扩展”的确切指南是什么?
我在微软官方文档中找不到这个答案。
到目前为止我发现了什么
查询优化器可以展开视图的情况:
- 这 Stack Exchange post 声称,一般来说,查询优化器将扩展 sql 视图。
查询优化器无法展开视图的情况:
- Predicate Pushing/Deferred Filtering - 但在 sql Server 2008 中已解决。
- Nested Views - 至少有时是这样。
灰色地带
- 此 Stack Overflow post's second answer 声明查询优化器可能会或可能不会扩展视图,具体取决于视图的复杂性和查询优化器的局限性。
解决方法
您不会在文档中找到此信息,因为它本身不是一个功能,它只是编译器/优化器通过查询工作的方式在各个阶段,使用多种不同的技术来获得最佳执行计划。有时它可以安全地推送谓词,有时则不能。
请注意,“扩展视图”在这里是错误的术语。视图总是扩展到它的定义中(NOEXPAND
除外)。您所指的称为谓词下推。
编译期间视图会发生什么变化?
我在这里假设没有使用索引视图和 NOEXPAND
。
当您执行查询时,编译器首先解析查询并将其词法化为基本执行计划。这是一个非常粗糙、未优化的版本,它几乎反映了所写的查询。
当查询中有视图时,编译器会检索视图的预解析执行树并将其推入执行计划中,这又是一个非常粗略的草稿。
对于派生表、CTE、相关和非相关子查询以及内联 TVF,会发生同样的事情,只是还需要解析。
在此之后,您可以假设视图也可以像 CTE 一样编写,没有区别。
优化器能否推送视图?
编译器有很多技巧,谓词下推就是其中之一,简化视图也是如此。
这里编译器的能力主要取决于它是否可以推断出简化是允许的,而不是它是可能的。
例如这个查询
SELECT SomeCol
FROM (
SELECT TOP 100 PERCENT *
FROM (
SELECT SomeCol,OtherCol,1 / 0 AS ThisDoesntError
FROM table1
) t
WHERE OtherCol = 1
ORDER BY ThisDoesntError
) t
WHERE OtherCol <> 2
对此进行优化是相当简单的
SELECT SomeCol
FROM table1
WHERE OtherCol = 1
因为已知 TOP 100 PERCENT... ORDER BY...
不会对外部查询产生任何影响,因此可以删除,然后是整个 ThisDoesntError
列。
那么它什么时候不起作用?
当优化器无法推送视图时,问题就开始了,因为它可能会改变查询的语义(以及结果)。
SELECT SomeCol
FROM (
SELECT TOP 10 *
FROM (
SELECT SomeCol,1 / 0 AS ThisDOESError
FROM table1
) t
ORDER BY ThisDOESError
) t
WHERE OtherCol = 1
因为TOP
需要根据ORDER BY ThisDOESError
子句进行计算,所以ThisDOESError
列不能被省略,OtherCol
上的过滤器不能被推送通过。
同样这个也无法优化
SELECT SomeCol
FROM (
SELECT SomeCol,ROW_NUMBER() OVER (PARTITION BY SomeCol ORDER BY ThirdCol) AS rn
FROM table1
) t
WHERE rn = 1 AND OtherCol = 1
在这种情况下,由于必须在整个集合上计算行号,过滤器 OtherCol = 5
不能安全地通过。
有趣的是,这个版本应该能够安全通过(虽然没有承诺!)
SELECT SomeCol
FROM (
SELECT SomeCol,ROW_NUMBER() OVER (PARTITION BY SomeCol ORDER BY ThirdCol) AS rn
FROM table1
) t
WHERE rn = 1 AND SomeCol = 'Something'
在这种情况下,优化器理论上应该能够看到过滤列也是分区列,因此行号计算不会改变。