这两个 SQL Server 回滚有何不同?

ToC*_*ToC 13 sql-server sql-server-2008-r2 database-internals rollback transaction-log

在 SQL Server 2008 R2 中,这两种回滚有何不同:

  1. 运行一条ALTER语句几分钟,然后点击“取消执行”。完全回滚需要几分钟时间。

  2. 运行相同的ALTER语句,但这要确保LDF文件不够大,无法成功完成。一旦达到LDF限制并且不允许“自动增长”,查询执行将立即停止(或发生回滚)并显示以下错误消息:

The statement has been terminated.
Msg 9002, Level 17, State 4, Line 1
The transaction log for database 'SampleDB' is full. 
To find out why space in the log cannot be reused, see the 
log_reuse_wait_desc column in sys.databases
Run Code Online (Sandbox Code Playgroud)

这两者在以下几点上有何不同?

  1. 为什么第二次“回滚”是瞬时的?我不完全确定它是否可以称为回滚。我的猜测是,事务日志是随着执行的进行而写入的,一旦它意识到没有足够的空间来完全完成任务,它就会停止并显示一些“结束”消息,而不提交。

  2. 当第一次回滚花费这么多时间(回滚单线程)时会发生什么?
    2.1. SQL Server 会返回并撤消LDF文件中的条目吗?
    2.2. 该LDF文件大小在回滚结束变得更小(从DBCC SQLPERF(LOGSPACE)

  3. 另一个问题:在第二种情况下,SQL Server 开始使用LDF文件的速度非常快。就我而言,它在前几分钟(< 4 分钟)内从 18% 的使用率增加到 90% 的使用率。但是一旦达到 99%,它又在那里停留了 8 分钟,同时使用率在 99.1% 到 99.8% 之间波动。在抛出错误之前,它会上升 (99.8%) 和下降 (99.2%) 并再次上升 (99.7%) 和下降 (99.5%)。幕后发生了什么?

任何可以帮助解释这一点的 MSDN 链接都值得赞赏。

在 Ali Razeghi 的建议下,我添加了 perfmon : Disk Bytes/sec

场景一:

场景一

场景2:

场景二

oou*_*ire 0

我尝试了以下实验并得到了类似的结果。在这两种情况下,fn_dblog() 都显示正在发生回滚,并且场景 2 中的回滚似乎比场景 1 中发生得更快。

顺便说一下,我将 MDF 和 LDF 放置在同一个外部 (USB 2.0) 磁盘上。

我的初步结论是,在这种情况下回滚操作没有差异,任何明显的速度差异可能都与 I/O 子系统有关。这只是我目前的工作假设。

场景一:

  • 创建一个数据库,其日志文件大小从 1MB 开始,以 4MB 块增长,最大大小为 100MB。
  • 打开显式事务,运行 10 秒,然后在 SSMS 中手动取消它
  • 查看 fn_dblog() 计数和日志保留大小并查看 DBCC SQLPERF(LOGSPACE)

场景2:

  • 创建一个数据库,其日志文件大小从 1MB 开始,以 4MB 块增长,最大大小为 100MB。
  • 打开显式事务,运行它直到出现日志已满错误
  • 查看 fn_dblog() 计数和日志保留大小并查看 DBCC SQLPERF(LOGSPACE)

性能监控结果:

场景一: ***场景1***

场景2: ***场景2***

代码:

使用[主控];
去

IF DATABASEPROPERTYEX (N'SampleDB', N'版本') > 0
开始
    更改数据库 [SampleDB] 设置单用户
        立即回滚;
    删除数据库[SampleDB];
结尾;
去

在主数据库上创建数据库 [SampleDB]
(
      NAME = N'SampleDB'
    , FILENAME = N'E:\data\SampleDB.mdf'
    ,大小 = 3MB
    , 文件增长 = 1MB
)
登录
(
      NAME = N'SampleDB_log'
    , FILENAME = N'E:\data\SampleDB_log.ldf'
    ,大小 = 1MB
    , 最大大小 = 100MB
    , 文件增长 = 4MB
);
去

使用[SampleDB];
去

-- 添加一个表
创建表 dbo.test
(
    c1 CHAR(8000) NOT NULL 默认复制('a',8000)
) 在[主要];
去

-- 确保我们不是伪简单的恢复模型
备份数据库 SampleDB
到磁盘='NUL';
去

-- 备份日志文件
备份日志SampleDB
到磁盘='NUL';
去

-- 检查已使用的日志空间
DBCC SQLPERF(日志空间);
去

-- fn_dblog() 有多少条记录可见?
SELECT * FROM fn_dblog(NULL,NULL); -- 我的情况是 9 左右

/*************************************
             场景1
**************************************/
-- 开启新事务然后回滚
开始交易

    插入 dbo.test 默认值;
    GO 10000 -- 让运行 10 秒,然后在 SSMS 查询窗口中点击“取消”

    -- 取消交易
    -- 需要几秒钟才能完成


-- 无需回滚事务,因为取消操作已经为您完成了该操作。
-  去尝试一下。你会得到这个错误
-- 消息 3903,第 16 级,状态 1,第 1 行
-- ROLLBACK TRANSACTION 请求没有对应的 BEGIN TRANSACTION。
回滚事务;

-- 使用的日志空间是多少?100%以上。
DBCC SQLPERF(日志空间);
去

-- fn_dblog() 有多少条记录可见?
选择 *
FROM fn_dblog(NULL,NULL); -- 以我为例,大约为 91,926

-- fn_dblog() 显示的总日志保留?
SELECT SUM([日志保留]) AS [总日志保留]
FROM fn_dblog(NULL,NULL); -- 约88.72MB


/*************************************
             场景2
**************************************/
-- 清除数据库并重新开始
使用[主控];
去

IF DATABASEPROPERTYEX (N'SampleDB', N'版本') > 0
开始
    更改数据库 [SampleDB] 设置单用户
        立即回滚;
    删除数据库[SampleDB];
结尾;
去

在主数据库上创建数据库 [SampleDB]
(
      NAME = N'SampleDB'
    , FILENAME = N'E:\data\SampleDB.mdf'
    ,大小 = 3MB
    , 文件增长 = 1MB
)
登录
(
      NAME = N'SampleDB_log'
    , FILENAME = N'E:\data\SampleDB_log.ldf'
    ,大小 = 1MB
    , 最大大小 = 100MB
    , 文件增长 = 4MB
);
去

使用[SampleDB];
去

-- 添加一个表
创建表 dbo.test
(
    c1 CHAR(8000) NOT NULL 默认复制('a',8000)
) 在[主要];
去

-- 确保我们不是伪简单的恢复模型
备份数据库 SampleDB
到磁盘='NUL';
去

-- 备份日志文件
备份日志SampleDB
到磁盘='NUL';
去

-- 现在,让我们炸毁事务中的日志文件
开始交易
    插入 dbo.test 默认值;
    去10000

-- 回滚永远不会触发。尝试一下。你会得到一个错误。
-- 消息 3903,第 16 级,状态 1,第 1 行
-- ROLLBACK TRANSACTION 请求没有对应的 BEGIN TRANSACTION。
回滚事务;

-- 日志文件是否已 100% 满?
DBCC SQLPERF(日志空间);

-- fn_dblog() 有多少条记录可见?
选择 *
FROM fn_dblog(NULL,NULL); -- 以我为例,大约为 91,926
去

-- fn_dblog() 显示的总日志保留?
SELECT SUM([日志保留]) AS [总日志保留]
FROM fn_dblog(NULL,NULL); -- 88.72MB
去