曲演杂坛--表变量的预估行数

在讨论临时表和表变量的区别时,其中一个重点就是两者的预估行数,在默认设置下,表变量的预估行数总是为1,而临时表的预估行数会随表中数据量的变化而变化。正是因为这个区别,在处理大数据量时往往推荐使用临时表而非表变量(当然还有索引的问题)。

 科普下, 查询优化器会根据预估行数和操作运算符来预估资源消耗,根据资源消耗情况来选取相对“较优”的执行计划,如果预估行数与实际行数差距较大,则可能生成不高效的执行计划。

举个栗子,看着远处的小土包没多远,骑着马跑了半天发现还没到,这就是看山跑死马的典故,如果能相对“准确”地预估出距离,那么就不是骑马而是开飞机,这就是预估行数影响执行计划!

今天就表变量的预估行数问题来学习下。

测试1:首先看下默认设置下表变量的预估行数

DECLARE @TB1 TABLE
(
    ID BIGINT IDENTITY(1,1) PRIMARY KEY,
    C1 BIGINT
)

INSERT INTO @TB1(C1)
SELECT object_id FROM sys.all_columns

SELECT COUNT(1) FROM @TB1 AS T1
INNER JOIN sys.objects AS T2
ON T1.C1 = T2.OBJECT_ID

通过上面的执行计划,很容易看到:临时表@TB1的实际行数为7490,而预估行数为1.

 

测试2:使用临时表

CREATE TABLE #TB1
(
    ID BIGINT IDENTITY(1,1) PRIMARY KEY,
    C1 BIGINT
)

INSERT INTO #TB1(C1)
SELECT object_id FROM sys.all_columns

SELECT COUNT(1) FROM #TB1 AS T1
INNER JOIN sys.objects AS T2
ON T1.C1 = T2.OBJECT_ID

从执行计划来看,临时表的预估行数和实际行数相同,均为7490.

 

测试3:使用OPTION (RECOMPILE)查询提示

DECLARE @TB1 TABLE
(
    ID BIGINT IDENTITY(1,1) PRIMARY KEY,
    C1 BIGINT
)

INSERT INTO @TB1(C1)
SELECT object_id FROM sys.all_columns

SELECT COUNT(1) FROM @TB1 AS T1
INNER JOIN sys.objects AS T2
ON T1.C1 = T2.OBJECT_ID
OPTION (RECOMPILE)

MSDN上对OPTION(RECOMPILE)的解释如下:

指示 SQL Server 数据库引擎在执行为查询生成的计划后将其丢弃,从而在下次执行同一查询时强制查询优化器重新编译查询计划。

在编译查询计划时,RECOMPILE 查询提示将使用查询中任意本地变量的当前值,如果查询位于存储过程中,这些当前值将传递给任意参数。

而在本测试中,使用OPTION(RECOMPILE)来使查询优化器对表变量有一个“准确”的预估行数,可以看到使用查询提示OPTION(RECOMPILE)下实际行数和预估行数均为7490。

PS: 通过测试1和测试3可以发现,随着预估行数的变化,执行计划也发生了变化。

 

测试4:使用跟踪标志2453

跟踪标志2453是SQL Server 2012 SP2和SQL Server CU3引入的,其作用于OPTION(RECOMPILE)类似,使得查询优化器在生成执行计划时对表变量有一个“准确”的预估行数,而不是简单粗暴地使用预估行数1。

DBCC TRACEON(2453)
DECLARE @TB1 TABLE
(
    ID BIGINT IDENTITY(1,1) PRIMARY KEY,
    C1 BIGINT
)

INSERT INTO @TB1(C1)
SELECT object_id FROM sys.all_columns

SELECT COUNT(1) FROM @TB1 AS T1
INNER JOIN sys.objects AS T2
ON T1.C1 = T2.OBJECT_ID

DBCC TRACEOFF(2453)

可以看到,在开启跟踪标志后,即使未使用查询提示OPTION(RECOMPILE),表变量的预估行数和实际行数均为7490。

 

测试5,跟踪标志2453的适用场景

一些文章介绍称跟踪标志2453仅使用与JOIN操作中,而对于一些“简单”查询,跟踪标志2453没有效果

最简单的查询莫过于SELECT FROM,如:

DBCC TRACEON(2453)
DECLARE @TB1 TABLE
(
    ID BIGINT IDENTITY(1,1) PRIMARY KEY,
    C1 BIGINT
)

INSERT INTO @TB1(C1)
SELECT object_id FROM sys.all_columns


SELECT COUNT(1) FROM @TB1


DBCC TRACEOFF(2453)

诚然,对于上面的查询,开启跟踪2453仍不能解决表变量预估行数为1的问题。

哪对于稍微复杂但又没有JOIN的查询呢?如:

DBCC TRACEON(2453)
DECLARE @TB1 TABLE
(
    ID BIGINT IDENTITY(1,1) PRIMARY KEY,
    C1 BIGINT
)

INSERT INTO @TB1(C1)
SELECT object_id FROM sys.all_columns


SELECT C1, COUNT(1) FROM @TB1 AS T1
GROUP BY C1

SELECT * FROM @TB1 AS T1
ORDER BY NEWID() DESC


DBCC TRACEOFF(2453)

可以发现,即使没有JOIN操作,开启跟踪2453会影响表变量的预估行数。

仔细分析下,对于简单的SELECT FROM操作,无表变量中数据量的数量多少,都只能进行聚集索引扫描,而对于后面两个查询,无论是GROUP BY还是ORDER BY,表变量中数据量不同,对数据进行排序所采用的算法会发生变化,因此推测:在开启跟踪2453条件下,如果表变量的数据量大小对最终生成的执行计划有影响,那么会对表变量返回一个“准确”的预估行数。

 

测试5,重编译问题

由于使用OPTION(RECOMPILE)会导致存储过程重编译,而频繁地重编译会大量消耗CPU资源,那么使用跟踪标志会导致重编译问题么?

本人未对该问题进行测试,从参考文章中了解到,跟踪标志不会导致存储过程频繁地发生重编译的问题,但如果随存储过程的传入参数不同,其内部使用的表变量的数据量存在严重变化的情况下,会引发存储过程重编译。打个碧玉,同样是出门,10公里内打车,50公里内打地铁,10000公里得打飞机,不同的距离导致不同的出行方式,不同的参数导致不同的执行计划。

虽然此跟踪标志没有引入频繁的重编译问题,同样也引入了新的问题,即不同参数需要使用不同执行计划,却因为没有重编译导致重用旧的执行计划引发性能问题。

查询提示OPTION(RECOMPILE)虽然导致每次都重编译,但却又能很好地防止“参数变化导致表变量的数据量发生变化最终生成不高效执行计划”的问题。

 

测试6,自定义表类型

既然表变量可以受影响于此跟踪标志,那么用户自定义的表类型呢?答案是自定义表类型同样可以受该跟踪标志的影响。

 

##==================================================##

总结:

由于跟踪标志2453的存在,使得我们可以在不修改任何代码的情况下,使查询获得类似于增加查询提示OPTION(RECOMPILE)的效果,同时还避免OPTION(RECOMPILE)带来的重复编译问题,而且跟踪标志可以在会话级别和实例级别进行设置,通过设置SQL Server的启动参数,可以轻松解决表变量的预估行数为1的问题,看似很美好,但是事物都有两面性,实例级别的跟踪标志需要进行严格的测试,同时需要验证不同参数对表变量的数据量的影响。

就个人而言,了解各种跟踪标志有利于我们进一步了解SQL Server本质,但在大多数情况下,我们要尽可能地避免使用跟踪标志,能用常规办法解决问题问题最好还是使用常规办法!

谁说的“能动手的话尽量别动嘴”!!!

##==================================================##

参考连接:

https://msdn.microsoft.com/zh-cn/library/ms181714.aspx

http://sqlperformance.com/2014/06/t-sql-queries/table-variable-perf-fix

##==================================================##

 

转载于:https://www.cnblogs.com/TeyGao/p/5612257.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值