SQL Server中TempDB管理(版本存储区的一个example)

本文通过实例介绍SQL Server中版本存储区的工作原理,包括如何创建行版本及何时回收这些版本。演示了在不同操作下版本存储区的变化情况,并探讨了其对性能的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文来自:

http://blogs.msdn.com/b/sqlserverstorageengine/archive/tags/tempdb/

http://blogs.msdn.com/b/sqlserverstorageengine/archive/2008/12/31/managing-tempdb-in-sql-server-tempdb-basics-version-store-simple-example.aspx

我们从一个例子开始来了解版本存储区。本例中数据库启用RCSI(使用行版本控制的已提交读隔离级别)和SI(快照隔离级别)。

当数据库启用了RCSI或者SI时,每当行有更新时就会创建一个行版本。此行版本在版本存储器保存到不需要为止。那么SQL Server什么时候不再保存这个行版本呢?您可能联想到一些相关的问题,比如,如果不回收(删除)行版本会有什么影响?一次回收一个还是一组行版本?每个用户表都有不同的结构,那么SQL Server是不是分别存储每个表的行版本的?行版本控制的开销大不大?

首先,我们创建一个数据库并开启SI/RCSI

create database snapshottest

go

 

-- Setting database for snapshot based isolations. AS you can

-- see that enabling SI/RCSI is a simple DDL operation.

alter database snapshottest set read_committed_snapshot ON

go

 

alter database snapshottest set allow_snapshot_isolation ON

go

 

-- check the snapshot state of the database and it will

-- show that both SI and RCSI have been enabled.

select  is_read_committed_snapshot_on,

   snapshot_isolation_state_desc,

        snapshot_isolation_state

from sys.databases

where name='snapshottest'

go

 

--create a table with each row > 1000 bytes

create table  t_version (c1 int, c2 char(1000))

go

 

--Load 50 rows. Note,I start the transaction but did not

--commit it yet.

Begin tran

declare @i int

select @i = 0

while (@i < 50)

begin

insert into t_version values (@i, 'hello')

set @i = @i + 1

end

 

接下来可以通过DMV查看版本存储区,你会发现显示版本存储区行数为0,这是我们实验的第一个结论,插入(Insert)命令不会产生行版本,因为根本没有之前的版本可言(有一个特例使插入产生行版本,我们将稍后讨论)。这同时意味着批量导入也不会产生行版本。

select COUNT(*) from sys.dm_tran_version_store

-- Now commit the transaction

commit

 

接下来让我们更新表格中的前50行。因为行数据被更新了,SQL Server将复制旧版本进入版本存储区。所以版本存储区中将有50行行版本。

-- update all the rows in the table

update t_version set c2 ='test10'

 

-- the following statement returns the count of versioned row.

-- And, for the case here, it will return 50

select COUNT(*) from sys.dm_tran_version_store

 

请注意,我们没有使用显式事务,因此这个Update语句是一个隐式事务,并且是这个隐式事务中唯一的语句。同时没有并发的事务需要使用行版本,所以这些行版本会被SQL Server当做垃圾数据处理。如果过几分钟在运行下面语句,就能发现行版本被回收处理了。

 

-- the following statement returns 0 if run after a minute or so

select COUNT(*) from sys.dm_tran_version_store

         SQL Server根据事务状态判断版本是否不再需要,从而决定垃圾处理该版本。在最差的情况下,如果有一个长事务正在运行,而且这个长事务要么创建了行版本要么需要用到它,那么这个行版本就不能被删除,版本存储区就会一直增长甚至会用尽TempDb的空间,就像长事务可以用尽事务日志空间一样。关于这一点我们会在稍后详述。

         和UPDATE操作一样,如果行被删除也会创建行版本。

 

<think>嗯,用户之前问过关于更新游标的使用方法,现在又接着问游标和存储过程、标量函数的别,特别是游标是否在对象资源管理器中找不到。这说明用户可能在使用游标时遇到了困惑,或者在数据库对象的管理上有疑问。用户提到“即写即用”,可能是在使用游标时发现它们不像存储过程或函数那样被持久化保存,所以需要确认游标的特性。 首先,我需要回顾一下游标的基本概念。游标是数据库中用于逐行处理查询结果集的机制,通常在T-SQL脚本中声明、打开、使用,然后关闭和释放。而存储过程和标量函数是预先定义并存储数据库中的对象,可以在对象资源管理器(比如SQL Server Management Studio)中看到。 接下来,用户的问题核心在于游标是否像存储过程和函数一样作为数据库对象存在,或者在资源管理器中是否可见。根据我的知识,游标是临时的,生命周期仅限于会话或批处理过程中,执行完毕后会被释放,所以不会持久化存储,因此在对象资源管理器中找不到它们。而存储过程和函数是作为数据库对象保存的,可以被多次调用,并且可以在资源管理器中查看和管理。 然后,我需要确认用户可能的误解点。用户可能认为游标和存储过程一样需要预先创建并保存,但实际上游标是在脚本中动态声明和使用的,不需要提前定义。这也解释了为什么在对象资源管理器中找不到游标的存在。 还需要考虑用户的实际应用场景。用户可能在编写复杂的更新逻辑时使用了游标,但发现无法像存储过程那样方便地管理和重复使用。这时候需要解释游标的临时性以及适用场景,同时建议在可能的情况下使用集合操作代替游标以提高性能。 此外,用户可能担心游标的管理和维护问题,比如如何跟踪已创建的游标。需要强调游标的临时性,使用后必须及时关闭和释放,避免资源泄露,这也是为什么在对象资源管理器中看不到它们的原因。 最后,要确保回答清晰,对比游标与存储过程、函数的不同,明确游标的临时性和非持久化特性,帮助用户正确理解和使用游标,避免误用或混淆。</think>--- ### 核心差异对比表 | 特性 | 游标 | 存储过程/函数 | |---------------------|------------------------------|-------------------------------| | **持久化存储** | ❌ 临时存在,执行后自动释放 | ✅ 存储数据库对象中 | | **资源管理器可见性** | ❌ 不可见 | ✅ 在"可编程性"目录下可见 | | **复用方式** | 即写即用,每次需重新声明 | 通过名称多次调用 | | **生命周期** | 从`DECLARE`到`DEALLOCATE` | 创建后永久存在,直到被删除 | --- ### 详细技术解析 #### 游标的"临时性"本质 游标更像**运行时的工作**,而非数据库对象。其生命周期遵循: ```sql DECLARE 游标 → OPEN → FETCH → CLOSE → DEALLOCATE ``` 整个过程都在**内存中完成**,不会在系统表(如`sys.objects`)留下记录。 #### 存储过程/函数的持久化特征 以SQL Server为例,创建后: ```sql CREATE PROCEDURE 示例过程 AS... CREATE FUNCTION 示例函数() RETURNS INT AS... ``` 会生成: - 物理存储:在`sys.sql_modules`中保存代码 - 元数据记录:出现在`sys.objects`的`type`列标记为`P`(过程)/`FN`(函数) --- ### 三种对象的使用场景对比 #### 游标适用场景 - 需要逐行处理的复杂业务逻辑 - 动态生成处理路径(如:根据前一行结果决定后续操作) - 示例代码: ```sql -- 临时处理订单状态迁移 DECLARE 订单游标 CURSOR FOR SELECT OrderID FROM Orders WHERE Status = 'Pending' OPEN 订单游标 FETCH... PROCESS... -- 即用即弃 CLOSE... DEALLOCATE ``` #### 存储过程适用场景 - 需要重复使用的业务逻辑封装 - 事务控制与错误处理 - 示例: ```sql CREATE PROC 每月结算 AS BEGIN TRANSACTION UPDATE 账户 SET 余额 = 余额 - 月租 ... COMMIT ``` 可在对象管理器找到并执行: ```sql EXEC 每月结算 ``` #### 函数适用场景 - 数据转换/计算(如:价格含税计算) - 必须返回单个值的场景 - 示例: ```sql CREATE FUNCTION 计算增值税(@金额 MONEY) RETURNS MONEY AS BEGIN RETURN @金额 * 1.13 END ``` 调用方式: ```sql SELECT 计算增值税(订单金额) FROM 订单表 ``` --- ### 开发中的实际表现差异 #### 在SSMS对象管理器中的位置 - **存储过程**:数据库 → 可编程性 → 存储过程 - **函数**:数据库 → 可编程性 → 函数 → 标量值函数 - **游标**:无处可寻 ![SSMS对象管理器结构示意图](https://example.com/ssms-object-explorer.png)*(示意图:展示存储过程和函数的位置)* #### 系统视图查询验证 ```sql -- 查找存储过程 SELECT * FROM sys.procedures -- 查找函数 SELECT * FROM sys.objects WHERE type IN ('FN', 'TF', 'IF') -- 标量/表值函数 -- 查找游标 → 无专门系统表 ``` --- ### 高级特性对比 | 功能 | 游标 | 存储过程/函数 | |---------------------|--------------------------|-----------------------------| | 参数传递 | ❌ 不支持 | ✅ 支持输入/输出参数 | | 返回结果 | 通过FETCH逐行获取 | 存储过程可返回结果集/函数返回标量 | | 事务控制 | 可嵌套在外部事务中 | 可自主控制事务边界 | | 执行计划缓存 | ❌ 每次重新编译 | ✅ 缓存优化 | | 权限控制 | 依赖基础表权限 | 可单独授权EXEC权限 | --- ### 为什么游标需要即写即用? #### 设计原理决定 游标本质是**对结果集的临时引用指针**。类比文件操作: ```python # 类似游标的工作方式 with open('data.txt') as f: # DECLARE + OPEN line = f.readline() # FETCH while line: # @@FETCH_STATUS检测 process(line) line = f.readline() # 退出with块自动关闭 → 类似DEALLOCATE ``` #### 与临时表的对比 | | 游标 | 临时表 | |----------------|--------------|----------------| | 存储形式 | 内存指针 | 物理存储(tempdb)| | 可见性 | 仅当前会话 | 会话间可共享 | | 索引支持 | ❌ | ✅ | --- ### 开发注意事项 1. **游标泄漏风险** 未正确关闭的游标会持续占用资源: ```sql -- 错误示例! DECLARE 游标... OPEN... -- 忘记CLOSE/DEALLOCATE ``` 正确做法: ```sql BEGIN TRY DECLARE ... OPEN... ...处理... END TRY BEGIN FINALLY CLOSE... DEALLOCATE -- 确保执行 END FINALLY ``` 2. **命名空间冲突** 不同会话可使用同名游标: ```sql -- 会话A DECLARE myCursor CURSOR FOR... -- 会话B 同时 DECLARE myCursor CURSOR FOR... -- 不会冲突 ``` 3. **性能监控** 通过DMV查看活动游标: ```sql SELECT session_id, name AS cursor_name, creation_time FROM sys.dm_exec_cursors(0) ``` --- ### 替代方案建议 当需要持久化类似游标的功能时,可考虑: 1. **使用表变量+WHILE循环** ```sql DECLARE @tempTable TABLE (ID INT, Data NVARCHAR(100)) INSERT INTO @tempTable SELECT... DECLARE @id INT WHILE EXISTS(SELECT 1 FROM @tempTable) BEGIN SELECT TOP 1 @id = ID FROM @tempTable -- 处理逻辑 DELETE FROM @tempTable WHERE ID = @id END ``` 2. **应用层分页处理** ```csharp // C# 示例 using (var reader = command.ExecuteReader()) { while (reader.Read()) { // 逐行处理 } } ``` --- **最终总结**: 游标是**瞬态的操作工具**,如同临时便签,用完即弃;存储过程和函数则是**永久工具箱**,存放在仓库(数据库)中随时取用。理解这一本质差异,能帮助开发者更合理地选择数据处理方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值