Answers:
对于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我得到一个合理的一致性:
@ Rows = 100时:
@ Rows = 1000时:
回到原来的问题。如果您要计算完成工作和提交所花费的时间,那么回滚是一件容易的事,因为大部分工作都花在寻找要更新的行上,而不是实际上在修改数据。如果您单独查看提交操作,那么应该很清楚,提交本身并没有什么“工作”。提交是“我完成了”。
begin tran
只会增加交易量。如果我了解您,rdbms会在COMMIT执行所有任务(联接行,生成执行计划...)?
回滚不只是“哦,没关系”-在很多情况下,确实必须撤消已经完成的工作。没有规则,回滚操作将始终比原始操作慢或总是比原始操作快,尽管即使原始事务并行运行,回滚也是单线程的。如果您正在等待,我建议保持等待是最安全的。
当然,这一切都随SQL Server 2019和Accelerated Database Recovery(加速数据库恢复)而改变(这也有变数,无论数据大小如何,都允许即时回滚)。
并非所有事务的提交活动都比回滚好得多。一种这样的情况是SQL中的删除操作。当事务删除行时,这些行被标记为幻像记录。一旦发出提交并开始执行幻像记录清除任务,则只会“删除”这些记录。
如果改为发出回滚,则只会从这些记录中删除幻影标记,而不是密集的插入语句。
并非全部。PostgreSQL进行回滚所需的时间不比提交所需的时间更多,因为这两个操作在磁盘I / O方面实际上是相同的。我实际上不认为这是针对提交进行了优化的问题,而是针对其他查询正在针对什么进行优化的问题。
基本问题是如何处理磁盘布局以及这如何影响提交与回滚。回滚比提交更慢的主数据库趋向于将数据(尤其是来自群集表的数据)移出主数据结构,并在更新数据时将其置于回滚段中。这意味着要提交,您只需删除回滚段,但要回滚,则必须复制所有数据。
对于PostgreSQL,所有表都是堆表,索引是分开的。这意味着在回滚或提交时,无需重新安排任何数据。这使得提交和回滚都很快。
但是,这会使其他一些事情变慢。例如,主键查找必须遍历索引文件,然后必须命中堆表(假定没有适用的覆盖索引)。这没什么大不了的,但是它确实增加了额外的页面查找,甚至可能添加了一些随机页面查找(如果该行上发生了很多更新)来检查其他信息和可见性。
但是,这里的速度不是PostgreSQL在写操作与读操作之间进行优化的问题。不愿意将某些读取操作优先于其他操作。因此,PostgreSQL的平均表现与其他数据库差不多。只是某些操作可能会更快或更慢。
因此,我认为实际的答案是数据库在读取方面针对某些工作负载进行了优化,这给写入方面带来了挑战。通常,在有问题的地方,提交通常比回滚更受欢迎,尽管不总是如此。但是,这取决于执行任一操作的含义(更新与删除不同)。