在上一篇文章里,我讨论了使用临时表如何引起SQL Server里的重编译。在文章最后我提到,今天这篇文章我会聚焦表变量(Table Variables)的更多信息,它可以避免重编译的昂贵开销。我们来详细分析下。

表变量(Table Variables)

表变量总局限于提交到SQL Server的批处理语句范围。当你在批处理语句范围外引用表变量时,SQL Server就会返回你一条错误信息。这是和临时表相比第1个重大区别。下列代码向你展示了如何创建和使用表变量——只在简单存储过程的上下文里。

 CREATE PROCEDURE DemonstrateTableVariablesNoRecompiles
AS
BEGIN
DECLARE @tempTable TABLE
(
ID INT IDENTITY(1, 1) PRIMARY KEY,
FirstName CHAR(4000),
LastName CHAR(4000)
) INSERT INTO @TempTable (FirstName, LastName)
SELECT TOP 1000 name, name FROM master.dbo.syscolumns SELECT * FROM @TempTable
END
GO

表变量的好处是它们不会引起任何重编译。当你执行这个存储过程并用SQL Server Profiler跟踪时,不会发现重编译事件。

 EXEC dbo.DemonstrateTableVariablesNoRecompiles

为什么使用表变量就可以这样呢?首先表变量就是个变量——名副其实。当你定义你的表变量时,意味着你不会改变你的数据库架构。因此基于数据酷架构改变的重编译就可以避免。另外表变量是没有统计信息的。因此没有统计信息需要维护,第2个引起重编译原因也就消失了。

首先,这2样听起来都很棒,但当我们进一步分析时,就会发现它的重大缺点。我们来看看。表变量近乎就是个变量。在临时表里,表变量还是持续的。是的,你没看错:当你使用表变量时,会涉及到临时表里的物理I/O操作。这个可以用动态管理视图sys.dm_db_session_space_usage来验证,它是在会话级别跟踪临时表的使用率。我们来看下面的代码(请【新建查询】执行下列代码):

 -- Create a table variable
DECLARE @tempTable TABLE
(
ID INT IDENTITY(1, 1) PRIMARY KEY,
FirstName CHAR(4000),
LastName CHAR(4000)
) -- Insert 4 records into the table variable
INSERT INTO @tempTable (FirstName, LastName) VALUES
(
'Woody',
'Tu'
),
(
'Woody',
'Tu'
),
(
'Woody',
'Tu'
),
(
'Woody',
'Tu'
) -- Retrieve the data from the table variable.
-- The execution plan estimates 1 row.
SELECT * FROM @tempTable
GO -- Review the space used in TempDb.
-- Our table variable currently needs 5 pages in TempDb.
-- The 5 needed pages from the table variable are already marked for deallocation (column "user_objects_dealloc_page_count")
SELECT * FROM sys.dm_db_session_space_usage
WHERE session_id = @@SPID
GO

从图中可以看出,这个表变量在临时表里需要分配5个页。因为这个表变量已经超过范围,这5个页面也已被标记为重分配(deallocation)。你要知道这个副作用。

表变量也没有统计信息。因此这里没有重编译发生。但是作为一个副作用,查询优化器始终认为估计行数为1.这个会非常,非常糟糕。如果你从表变量连接你数据库里另外一张表。在那个情况下,查选优化器在执行计划里引入嵌套循环连接(Nested Loop Join)运算符,引用的表变量作为外表,因为估计行数是1。如果事实上返回行是10000或更多的话,整个执行计划就谈不上最优。我们来看下面的例子(点击工具栏的显示包含实际的执行计划):

 CREATE PROCEDURE BadPerformingQuery
AS
BEGIN
DECLARE @tempTable TABLE
(
ID INT IDENTITY(1, 1) PRIMARY KEY,
FirstName CHAR(4000),
LastName CHAR(4000)
) INSERT INTO @TempTable (FirstName, LastName)
SELECT TOP 20000 name, name FROM master.dbo.syscolumns -- The physical Join Operator will be a Nested Loop,
-- because Nested Loop is optimized for 1 row in the outer loop.
SELECT * FROM AdventureWorks2008R2.Person.Person p
INNER JOIN @tempTable t ON t.ID = p.BusinessEntityID
END
GO

我们仔细看下聚集索引扫描( Clustered Index Scan)运算符的属性信息,你会看到这里的估计行数是1,而实际行数却是12622。

你可以通过自SQL Server 2005起引入的语句级别的重编译(Statement-Level Recompilation)来修正这个基数预估错误。

 -- Use a statement-level recompilation to fix the problem with the
-- cardinality estimation.
ALTER PROCEDURE BadPerformingQuery
AS
BEGIN
DECLARE @tempTable TABLE
(
ID INT IDENTITY(1, 1) PRIMARY KEY,
FirstName CHAR(4000),
LastName CHAR(4000)
) INSERT INTO @TempTable (FirstName, LastName)
SELECT TOP 20000 name, name FROM master.dbo.syscolumns -- The physical Join Operator will be a Nested Loop,
-- because Nested Loop is optimized for 1 row in the outer loop.
SELECT * FROM AdventureWorks2008R2.Person.Person p
INNER JOIN @tempTable t ON t.ID = p.BusinessEntityID
OPTION (RECOMPILE)
END
GO

但是这个方法有点产生相反效果的(counter-productive),因为你又引入了重编译,原先你使用表变量就是为了避免重编译。

小结

使用表变量你可以避免SQL Server里重编译的负荷,但同样也有副作用。最大的副作用就是错误参数估计——估计行数为1。因此当你和小数量行打交道时可以使用表变量,因为那时错误的基数预估并不重要,也不影响你的性能。但和大量数据行打交道时,它会伤害你的性能,因为生成了低效的执行计划。

作为通常的经验法则(general rule-of-thumb),对于大数量的数据,你应该使用临时表,表变量用在小数量的数据上。但是你真的要为你的工作量测试(benchmark)下,来决定什么时候使用临时表,什么时候使用表变量是正确的。

参考文章:

https://www.sqlpassion.at/archive/2014/11/16/recompilations-part-2-2/

探秘重编译(Recompilations)(2/2)的更多相关文章

  1. 探秘重编译(Recompilations)(1/2)

    这篇文章我想谈下SQL Server里一个非常重要的性能调优话题:重编译(Recompilations) .当你执行非常简单的存储过程(使用临时表)时,就会发生.今天我想奠定SQL Server里重编 ...

  2. SQL SERVER 临时表导致存储过程重编译(recompile)的一些探讨

    SQLSERVER为了确保返回正确的值,或者处于性能上的顾虑,有意不重用缓存在内存里的执行计划,而重新编译执行计划的这种行为,被称为重编译(recompile).那么引发存储过程重编译的条件有哪一些呢 ...

  3. sqlserver 存储过程中使用临时表到底会不会导致重编译

    曾经在网络上看到过一种说法,SqlServer的存储过程中使用临时表,会导致重编译,以至于执行计划无法重用, 运行时候会导致重编译的这么一个说法,自己私底下去做测试的时候,根据profile的跟踪结果 ...

  4. [译]SQL Server 之 查询计划缓存和重编译

    查询优化是一个复杂而且耗时的操作,所以SQL Server需要重用现有的查询计划.查询计划的缓存和重用在多数情况下是有益的的,但是在某些特殊的情况下,重编译一个查询计划可能能够改善性能. SELECT ...

  5. DXperience重编译汉化的方法

    1. 必须有DXperience相应版本的全部源代码SourceCode.把全部源代码复制到\Program Files\Developer Express .NET vX.X\Sources目录.目 ...

  6. SQL 编译与重编译

    编译的含义 当SQLSERVER收到任何一个指令,包括查询(query).批处理(batch).存储过程.触发器(trigger) .预编译指令(prepared statement)和动态SQL语句 ...

  7. 关于T-SQL重编译那点事,WITH RECOMPILE和OPTION(RECOMPILE)区别仅仅是存储过程级重编译和SQL语句级重编译吗

    本文出处:http://www.cnblogs.com/wy123/p/6262800.html   在考虑重编译T-SQL(或者存储过程)的时候,有两种方式可以实现强制重编译(前提是忽略导致重编译的 ...

  8. 关于T-SQL重编译那点事,内联函数和表值函数在编译生成执行计划的区别

    本文出处:http://www.cnblogs.com/wy123/p/6266724.html 最近在学习 WITH RECOMPILE和OPTION(RECOMPILE)在重编译上的区别的时候,无 ...

  9. SQL Server 执行计划重编译的两大情况

    1.与正确性相关的重编译 1.为表或视图添加列,删除列. 2.为表添加约束.默认值.规则,删除约束.默认值.规则. 3.为表或视图添加索引. 4.如果计划用不用索引而这个索引被删除. 5.删除表中的统 ...

随机推荐

  1. 详解spring事务属性

    Spring声明式事务让我们从复杂的事务处理中得到解脱.使得我们再也无需要去处理获得连接.关闭连接.事务提交和回滚等这些操作.再也无需要我们在与事务相关的方法中处理大量的try…catch…final ...

  2. Asp.net vnext的IIS部署

    微软前段时间推出asp.net vnext,自个也尝试了一把,也写了几篇关于vnext特性的博客,如下. 但一直有个困扰的问题,asp.net vnext如何在iis部署,因为整个目录结构跟之前有了很 ...

  3. 【视频教程】使用UIAutomation开发软件外挂

    UIAutomation是.Net 3.5之后提供的“界面自动化测试”技术,本来是给测试人员用的,不过UIAutomation由于也是界面自动操作的技术,比直接使用keybd_event.GetWin ...

  4. apache httpclient cache 实现可缓存的http客户端

    这里的cache storage 采用ehcache,而不是默认的内存式的cache storage.采用ehcache可以将内容缓存到磁盘上. maven <dependency> &l ...

  5. 用JQ仿造百度书籍预售页面的单屏滚页效果

    今天的项目需要做到一个介绍页面,我主动提出走单屏滚页的风格,毕竟交互性好,逼格也高,具体效果可以参照百度知道书籍预售页面. 其实现效果就大概是这样的: 还是老样子,所有步骤文件可以从我的Github上 ...

  6. 假如现在有一堆长度大于3小于9的电话号码,用座机呼叫,如果出现这样的号码【123和12345】那么12345将永远不会被拨出,因为拨到123的时候电话已经呼出了,试写一个函数输出所有不能被呼出的电话号码(java实现)

    解题: 假如现在有一堆长度大于3小于9的电话号码,用座机呼叫,如果出现这样的号码[123和12345]那么12345将永远不会被拨出,因为拨到123的时候电话已经呼出了,试写一个函数输出所有不能被呼出 ...

  7. PosePlus的第一次突破

    动作问题一定要解决 PosePlus是解决这个问题的库 (之前叫做CleanData.Ani,后来我想过,之前的设计各个库之间的依赖太重了) 现在把他们之间的依赖剥开.   PosePlus解决几个问 ...

  8. Redis教程(十三):管线详解

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/141.html 一.请求应答协议和RTT: Redis是一种典型的基于C/ ...

  9. DDD~WCF做中间件,实现多个项目的缓存共享

    回到目录 事情是这样的,前台网站有些数据不希望每次都从数据库里读,所以,应该做个缓存,而引起缓存更新的入口来自网站的后台管理,而前台和后台被部署在不同的网站中,这时缓存的更新就成了问题,前台的缓存与后 ...

  10. gulp:更简单的自动化构建工具

    目前最流行的两种使用JavaScript开发的构建工具是Grunt和Gulp.为什么使用gulp?因为Gulp更简单.Grunt任务拥有大量的配置,会引用大量你实际上并不需要的对象属性,但是Gulp里 ...