ROLLBACK是快速操作吗?


Answers:


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

运行一个“不良”更新查询,测量完成工作所花费的时间以及发出提交所花费的时间。

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

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

    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

随着@ Rows = 1我得到一个合理的一致性:

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

@ Rows = 100时:

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

@ Rows = 1000时:

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

回到原来的问题。如果您要计算完成工作和提交所花费的时间,那么回滚是一件容易的事,因为大部分工作都花在寻找要更新的行上,而不是实际上在修改数据。如果您单独查看提交操作,那么应该很清楚,提交本身并没有什么“工作”。提交是“我完成了”。


2
“更少的工作” 不一定是 “更快的”
杰克·道格拉斯

我知道这begin tran只会增加交易量。如果我了解您,rdbms会在COMMIT执行所有任务(联接行,生成执行计划...)?
加里克2011年

3
不,所有工作都在提交之前完成。提交操作本身做得很少。
Mark Storey-Smith

@Mark我已经做了一些粗略的准备测试,插入了2m行,然后提交或回滚。包括回滚的总时间从10s到30s不等,而包括提交的总时间在6s到14s之间。YMMV当然可以,但是这表明,至少在我的环境中,回滚的时间几乎是原始交易的时间。
杰克·道格拉斯,

2
如果您要测量提交操作完成的时间,我希望这是最少的,除非同时发出了一个检查点(这是相互独立且无关的)。这就是我的观点,提交所做的很少,而回滚则完成了提交之前发生的所有事情以及更多内容。测试中的差异可能会影响其他因素,但我稍后一定会尝试将一些脚本放在一起。
马克·斯托里·史密斯

13

对于Oracle,回滚所花费的时间比进行回滚的更改所花费的时间长很多倍。这通常并不重要,因为

  1. 事务回滚时不保留任何锁
  2. 它由低优先级后台进程处理

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

至于“为什么”,我想说的rollback应该很少见,通常仅在出现问题时才会出现,当然commit很可能更常见-因此针对commit


9

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

当然,这一切都随SQL Server 2019和Accelerated Database Recovery加速数据库恢复)而改变(这也有变数,无论数据大小如何,都允许即时回滚)。


2
在某个时候,我们所有人都有过“花很多时间才能回滚,让我们重新启动它”的对话吗?
Mark Storey-Smith,

我已经看到很多客户这样做。有些相对毫发无损地出来,有些则不那么幸运。
阿龙贝特朗

1
@ MarkStorey-Smith-如果您在回滚过程中重新引导,SQL Server是否不必在启动时继续其回滚?
Nick Chammas,

2
@Nick取决于-例如,如果回滚在重新启动之前被阻止,则在重新启动服务后它的行为可能会更快,因为其他进程刚刚被杀死。在这种情况下,有很多“假设”-每当您重新启动服务器或重新启动服务以“解决”问题时,可能就会遇到一些更严重的问题。
亚伦·伯特兰

2
@Nick,是的,这正是发生的情况。我的评论原意是“面露舌头”,以至于您不可避免地不得不不得不向触发这些快乐的人进行解释,这些人希望在某些情况下无法按预期进行重新启动。
Mark Storey-Smith

8

并非所有事务的提交活动都比回滚好得多。一种这样的情况是SQL中的删除操作。当事务删除行时,这些行被标记为幻像记录。一旦发出提交并开始执行幻像记录清除任务,则只会“删除”这些记录。

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


如何优化某些操作以进行回滚的好例子。
Mark Storey-Smith,

5

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

基本问题是如何处理磁盘布局以及这如何影响提交与回滚。回滚比提交更慢的主数据库趋向于将数据(尤其是来自群集表的数据)移出主数据结构,并在更新数据时将其置于回滚段中。这意味着要提交,您只需删除回滚段,但要回滚,则必须复制所有数据。

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

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

但是,这里的速度不是PostgreSQL在写操作与读操作之间进行优化的问题。不愿意将某些读取操作优先于其他操作。因此,PostgreSQL的平均表现与其他数据库差不多。只是某些操作可能会更快或更慢。

因此,我认为实际的答案是数据库在读取方面针对某些工作负载进行了优化,这给写入方面带来了挑战。通常,在有问题的地方,提交通常比回滚更受欢迎,尽管不总是如此。但是,这取决于执行任一操作的含义(更新与删除不同)。


很好的答案,但有一点点疑问:“对于PostgreSQL,所有表都是堆表,索引是分开的。这意味着在回滚或提交时,无需重新排列数据”,这不是没有数据必须重新排列的原因。进行重新排列,是因为“主数据库的回滚速度比提交速度要慢,而db往往会移动数据”,而pg却没有,正如您提到的。Oracle还默认使用堆存储:主要区别是Oracle使用'undo'并在提交/回滚时回收所有空间,而不是使用'vacuum'路由。
杰克·道格拉斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.