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
对于 SQL Server,您可能会争辩说,提交操作只不过是将 LOP_COMMIT_XACT 写入日志文件并释放锁,这当然会比事务自 BEGIN TRAN 以来执行的每个操作的 ROLLBACK 更快。
如果您正在考虑事务的每个操作,而不仅仅是提交,我仍然认为您的陈述不正确。排除外部因素,例如日志磁盘的速度与数据磁盘的速度相比,事务完成的任何工作的回滚都可能比首先完成的工作要快。
回滚是读取更改的顺序文件并将它们应用到内存数据页。最初的“工作”必须生成执行计划、获取页面、连接行等。
编辑:这取决于位...
@JackDouglas 指出这篇文章描述了回滚可能比原始操作花费更长的时间的情况之一。该示例是一个 14 小时的事务,不可避免地使用并行性,这需要 48 多个小时才能回滚,因为回滚主要是单线程的。您很可能还会反复搅动缓冲池,因此您不再需要反转对内存页面的更改。
所以,我之前的答案的修订版。回滚慢了多少?考虑到所有其他因素,对于典型的 OLTP 事务来说,情况并非如此。在典型范围之外,“撤消”可能比“做”花费更长的时间,但是(这是一个潜在的绕口令吗?)为什么将取决于“做”是如何完成的。
Edit2:继评论中的讨论之后,这里有一个非常人为的例子来证明正在完成的工作是确定提交与回滚作为操作的相对成本的主要因素。
创建两个表并低效地打包它们(每页浪费空间):
运行“错误”更新查询,测量完成工作所花费的时间和发出提交所花费的时间。
再次执行相同操作,但发出并测量回滚。
使用 @Rows=1 我得到一个相当一致的结果:
使用@Rows=100:
使用@Rows=1000:
回到最初的问题。如果您要测量完成工作和提交所花费的时间,那么回滚就是胜出,因为大部分工作都用于查找要更新的行,而不是实际修改数据。如果您正在孤立地查看提交操作,那么应该清楚提交所做的“工作”很少。提交是“我完成了”。
对于 Oracle,回滚所花费的时间可能比进行回滚更改所花费的时间长很多倍。这通常无关紧要,因为
对于 SQL Server,我不确定情况是否相同,但其他人会说如果不是......
至于“为什么”,我会说
rollback
应该是罕见的,通常只有在出现问题的情况下,当然commit
可能更常见 - 所以优化是有意义的commit
回滚不仅仅是“哦,没关系”——在很多情况下,它确实必须撤消已经完成的操作。没有规定回滚操作总是比原始操作慢或总是快,尽管即使原始事务并行运行,回滚也是单线程的。如果您正在等待,我建议您继续等待是最安全的。
当然,这一切都随着 SQL Server 2019 和Accelerated Database Recovery发生了变化(其代价也是可变的,允许即时回滚,而不管数据大小如何)。
并非所有事务的提交活动都会比回滚更好。一种这样的情况是 SQL 中的删除操作。当事务删除行时,这些行被标记为幽灵记录。一旦发出提交并且幽灵记录清理任务开始,那么只有这些记录被“删除”。
如果改为发出回滚,它只会从这些记录中删除重影标记,而不是密集的插入语句。
不是所有的都是。PostgreSQL 回滚不需要比提交更多的时间,因为这两个操作在磁盘 I/O 方面实际上是相同的。我实际上并不认为这是一个针对提交进行优化的问题,而是一个针对其他查询进行优化的问题。
基本问题是如何处理磁盘布局以及这如何影响提交与回滚。回滚速度比提交慢的主要数据库倾向于将数据(尤其是来自聚集表的数据)移出主要数据结构,并在更新数据时将其放入回滚段中。这意味着要提交,您只需删除回滚段,但要回滚,您必须将所有数据复制回来。
对于 PostgreSQL,所有表都是堆表,索引是独立的。这意味着在回滚或提交时,无需重新排列任何数据。这使得提交和回滚都很快。
但是,它会使其他一些事情变慢一些。例如,主键查找必须遍历索引文件,然后必须命中堆表(假设没有适用的覆盖索引)。这没什么大不了的,但它确实添加了额外的页面查找,甚至可能添加了一些随机页面查找(如果该行发生了很多更新)以检查其他信息和可见性。
然而,这里的速度不是 PostgreSQL 中写操作与读操作的优化问题。不愿意将某些读取操作的特权置于其他操作之上。因此 PostgreSQL 的平均性能与其他数据库一样好。只是某些操作可能更快或更慢。
所以我认为实际的答案是数据库针对读取端的某些工作负载进行了优化,这导致了写入端的挑战。通常在有问题的地方,提交通常(尽管并非总是)会比回滚更受青睐。然而,这取决于执行任一操作的含义(更新与删除不同)。