在建立临时表的TVF上加入连接要比直接子句快得多。为什么会这样?

时间:2018-10-12 14:45:32

标签: sql-server indexing sql-server-2012 sql-server-2016

我们正在优化看起来设计不佳的查询。有一个到多行表值函数的联接,该联接很容易成为内联函数,因此我们对其进行了更新..但是查询变得慢得多。多行函数返回一个以单列为主键的临时表,而内联TVF则返回一个单列直接表。

进行一些研发,我们发现使用合并的多行TVF作为子句可以显着提高性能。以下示例可以在任何数据库上运行,最好具有悠久的对象更新历史。我们认为第一个查询是最好的设计,但是第二个查询的性能要好得多(通常快20倍)。 这实际上是查询费用,而不是速度

create function udf_ObjectsModifiedBetweenDates
(
    @DateFrom datetime,
    @Dateto datetime
)
returns @t table(object_id int primary key)
as
begin
insert into @t
(
    object_id
)
select object_id
from sys.objects 
where modify_date between @DateFrom and @Dateto
return
end

GO

declare @datefrom datetime = '2017-05-01' --Please adjust these dates to get good sample
declare @dateto datetime = '2018-08-02'

--slow
select object_id, parent_object_id, is_ms_shipped
from sys.objects
where modify_date between @datefrom and @dateto option (recompile)

--fast
select o.object_id, o.parent_object_id, o.is_ms_shipped
from sys.objects o
inner join udf_ObjectsModifiedBetweenDates(@datefrom, @dateto) ombd on o.object_id = ombd.object_id option (recompile)

函数和查询与我们在系统上处理的函数和查询类似,但是在这里我们用系统表代替了用户表。我们已经在其他用户表上尝试了同样的方法,再次从MTVF中获得了巨大收益。 执行计划表明,在TVF上存在针对较快查询的聚集索引扫描。 谁能解释正在发生的事情,这是否是提高查询性能的可行方法?

enter image description here

1 个答案:

答案 0 :(得分:1)

似乎您在执行计划中误将“查询成本(相对于批次)”解释为“查询成本(查询将花费多长时间运行)”。查询的成本与查询的效率或速度几乎没有关系。

我同时运行以下查询(更改日期):

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

对于您标记为“慢”(没有UDF的查询)的查询,结果为:

The "slow" one

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 8 ms, elapsed time = 8 ms.

(19305 rows affected)
Table 'sysschobjs'. Scan count 1, logical reads 379, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row affected)

 SQL Server Execution Times:
   CPU time = 62 ms,  elapsed time = 61 ms.

对于标记为“快速”(UDF)的那个,输出为:

The "fast" one

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 7 ms.

(19305 rows affected)
Table 'sysschobjs'. Scan count 0, logical reads 38610, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#B1BBD6DD'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row affected)

 SQL Server Execution Times:
   CPU time = 266 ms,  elapsed time = 329 ms.

从这些统计数据可以看出,到目前为止,非UDF版本是赢家。非UDF的执行时间快了5倍以上,但是,UDF查询的IO太疯狂了(读取超过1000倍)。

老实说,多行表值函数比通常的性能杀手更重要。在3种类型的函数(内联表值,标量值和多行表值)中,它们可能是最慢的,而内联通常是最快的。在传闻中,我听说标量函数在SQL Server 2019中是“更好”的,但是,我还没有进行自我测试,也没有看到任何实际证据。只是在交谈中。