ROLLBACK 是快速操作吗?

gar*_*rik 20 rdbms performance sql-server-2008 transaction rollback

RDBMS 系统是否针对COMMIT操作进行了优化?ROLLBACK操作慢/快多少?为什么?

Mar*_*ith 14

对于 SQL Server,您可能会争辩说,提交操作只不过是将 LOP_COMMIT_XACT 写入日志文件并释放锁,这当然比 BEGIN TRAN 之后事务执行的每个操作的 ROLLBACK 更快。

如果您正在考虑事务的每个操作,而不仅仅是提交,我仍然认为您的陈述是不正确的。排除外部因素,例如与数据磁盘速度相比,日志磁盘的速度,事务完成的任何工作的回滚可能会比最初的工作更快。

回滚是读取更改的顺序文件并将它们应用到内存数据页。最初的“工作”必须生成执行计划、获取页面、连接行等。

编辑:这取决于位...

@JackDouglas 指出了这篇文章,它描述了回滚可能比原始操作花费的时间长得多的一种情况。这个例子是一个 14 小时的事务,不可避免地使用并行性,需要 48 多个小时来回滚,因为回滚主要是单线程的。您很可能还会反复搅动缓冲池,因此您不再反转对内存页面的更改。

所以,我之前的答案的修订版。回滚慢多少?考虑到所有其他因素,对于典型的 OLTP 事务而言,情况并非如此。在典型的范围之外,“撤消”可能比“做”需要更长的时间,但是(这是一个潜在的绕口令吗?)为什么将取决于“做”是如何完成的。

Edit2:继评论中的讨论之后,这里有一个非常人为的例子来证明正在完成的工作是确定提交与回滚作为操作的相对费用的主要因素。

创建两个表并低效地打包它们(每页浪费空间):

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
SET NOCOUNT ON;
GO

CREATE TABLE dbo.Foo
(
    col1 INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , col2 CHAR(4000) NOT NULL DEFAULT REPLICATE('A', 4000)
)

CREATE TABLE dbo.Bar
(
    col1 INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , col2 CHAR(4000) NOT NULL DEFAULT REPLICATE('A', 4000)
)
GO

INSERT dbo.Foo DEFAULT VALUES
GO 100000

INSERT dbo.Bar DEFAULT VALUES
GO 100000
Run Code Online (Sandbox Code Playgroud)

运行“坏”更新查询,测量完成工作所需的时间和发出提交所需的时间。

DECLARE 
    @StartTime DATETIME2
    , @Rows INT

SET @Rows = 1

CHECKPOINT
DBCC DROPCLEANBUFFERS

BEGIN TRANSACTION

SET @StartTime = SYSDATETIME()

UPDATE
    dbo.bar
SET
    col2 = REPLICATE('B', 4000)
FROM
    dbo.bar b
INNER JOIN
    (
    SELECT TOP(@Rows)
        col1
    FROM
        dbo.foo
    ORDER BY
        NEWID()
    ) f
ON  f.col1 = b.col1
OPTION (MAXDOP 1)

SELECT 'Find and update row', DATEDIFF(ms, @StartTime, SYSDATETIME())

SET @StartTime = SYSDATETIME()

COMMIT TRANSACTION

SELECT 'Commit', DATEDIFF(ms, @StartTime, SYSDATETIME())
GO
Run Code Online (Sandbox Code Playgroud)

再次执行相同操作,但发出并测量回滚。

    DECLARE 
    @StartTime DATETIME2
    , @Rows INT

SET @Rows = 1

CHECKPOINT
DBCC DROPCLEANBUFFERS

BEGIN TRANSACTION

SET @StartTime = SYSDATETIME()

UPDATE
    dbo.bar
SET
    col2 = REPLICATE('B', 4000)
FROM
    dbo.bar b
INNER JOIN
    (
    SELECT TOP(@Rows)
        col1
    FROM
        dbo.foo
    ORDER BY
        NEWID()
    ) f
ON  f.col1 = b.col1
OPTION (MAXDOP 1)

SELECT 'Find and update row', DATEDIFF(ms, @StartTime, SYSDATETIME())

SET @StartTime = SYSDATETIME()

ROLLBACK TRANSACTION

SELECT 'Rollback', DATEDIFF(ms, @StartTime, SYSDATETIME())
GO
Run Code Online (Sandbox Code Playgroud)

使用@Rows=1 我得到了相当一致的结果:

  • 5500ms 用于查找/更新
  • 3ms 提交
  • 1ms 回滚

使用@Rows=100:

  • 8500ms 查找/更新
  • 15 毫秒提交
  • 15ms 回滚

使用@Rows=1000:

  • 15000 毫秒查找/更新
  • 10 毫秒提交
  • 500ms 回滚

回到最初的问题。如果您正在测量完成工作和提交所花费的时间,那么回滚将胜出,因为大部分工作都用于查找要更新的行,而不是实际修改数据。如果您孤立地查看提交操作,那么应该很清楚提交所做的“工作”很少。提交是“我完成了”。

  • 不,所有的工作都是在提交之前完成的。提交操作本身做的相对较少。 (3认同)
  • “少工作”是[不一定](http://blogs.msdn.com/b/psssql/archive/2008/09/12/sql-server-2000-2005-2008-recovery-rollback-taking-longer-比预期的.aspx)'更快' (2认同)
  • 如果您要测量提交操作完成的时间,我希望它是最少的,除非恰好同时发出检查点(这是单独且不相关的)。这就是我的观点,提交所做的很少,而回滚完成提交之前发生的所有事情,再加上更多。您的测试中的差异表明其他因素在起作用,但我稍后肯定会尝试将一些脚本放在一起。 (2认同)

Jac*_*las 13

对于 Oracle 而言,回滚所花费的时间可能比进行回滚更改所花费的时间长很多倍。这通常无关紧要,因为

  1. 事务回滚时不持有锁
  2. 它由一个低优先级的后台进程处理

对于 SQL Server,我不确定情况是否相同,但其他人会说如果不是...

至于“为什么”,我会说rollback应该很少见,通常只有在出现问题的情况下,当然commit可能更常见 - 所以优化是有意义的commit


Aar*_*and 9

回滚不仅仅是“哦,没关系” - 在很多情况下,它确实必须撤消已经完成的操作。没有规定回滚操作总是比原始操作慢或总是快,尽管即使原始事务并行运行,回滚也是单线程的。如果您正在等待,我建议继续等待是最安全的。

当然,这一切都随着 SQL Server 2019 和加速数据库恢复而改变(它的代价也是可变的,允许即时回滚,无论数据大小如何)。

  • 我们都曾在某个时候进行过“回滚需要很长时间,让我们重新启动它”的对话,对吗? (2认同)
  • @Nick 视情况而定-例如,如果回滚在重新启动之前被阻止,则在重新启动服务后它的行为可能会快得多,因为其他进程刚刚被终止。在这种情况下有很多“假设” - 每当您重新启动服务器或重新启动服务以“修复”问题时,可能会有一些更严重的问题在起作用。 (2认同)
  • @Nick,是的,这正是发生的事情。我的评论旨在“吐槽”,以至于您不可避免地最终不得不向触发快乐的人解释这一点,他们想要在某些事情不按预期运行时重启。 (2认同)

Sta*_*hns 8

并非所有事务的提交活动都比回滚执行得更好。其中一种情况是 SQL 中的删除操作。当事务删除行时,这些行被标记为幽灵记录。一旦提交被发出并且幽灵记录清理任务开始,那么只有这些记录被“删除”。

如果改为发出回滚,它只会从这些记录中删除幽灵标记,而不是密集的插入语句。


Chr*_*ers 5

并非所有都是。PostgreSQL 回滚所需的时间不会比提交时间多,因为这两个操作在磁盘 I/O 方面实际上是相同的。我实际上并不认为这是一个针对提交进行优化的问题,而是一个正在优化其他查询的问题。

基本问题是如何解决磁盘布局以及这如何影响提交与回滚。回滚比提交更慢的主数据库倾向于将数据,特别是从聚集表中的数据移出主数据结构,并在更新数据时将其放入回滚段。这意味着要提交您只需删除回滚段,但要回滚您必须复制所有数据。

对于 PostgreSQL,所有表都是堆表,索引是分开的。这意味着在回滚或提交时,无需重新排列数据。这使得提交和回滚都很快。

但是,它会使其他一些事情变慢。例如,主键查找必须遍历索引文件,然后它必须命中堆表(假设没有适用的覆盖索引)。这没什么大不了的,但它确实添加了额外的页面查找,甚至可能是一些随机页面查找(如果该行上发生了大量更新)以检查其他信息和可见性。

然而,这里的速度不是 PostgreSQL 中写操作与读操作的优化问题。不愿意将某些读取操作置于其他读取操作之上。因此,PostgreSQL 的平均性能与其他数据库一样好。只是某些操作可能更快或更慢。

所以我认为实际的答案是数据库针对读取端的某些工作负载进行了优化,这导致了写入端的挑战。通常在有问题的地方,提交通常(尽管并不总是)比回滚更受青睐。然而,这取决于执行任一操作的含义(更新与删除不同)。