了解基于行版本控制的隔离级别

行版本控制是 SQL Server 中用于执行下列操作的常规框架:

  • 生成触发器中插入的删除的表。对任何由触发器修改的行都将生成副本。这包括由启动触发器的语句修改的行,以及由触发器进行的任何数据修改。

  • 支持多个活动的结果集 (MARS)。如果 MARS 会话发出一条数据修改语句(例如 INSERT、UPDATE 或 DELETE)时存在活动的结果集,则对受修改语句影响的行都将生成副本。

  • 支持指定 ONLINE 选项的索引操作。

  • 支持基于行版本控制的事务隔离级别:

    • 新实现的已提交读隔离级别,使用行版本控制提供语句级的读取一致性。

    • 新快照隔离级别,提供事务级的读取一致性。

tempdb 数据库必须具有足够的空间用于版本存储区。在 tempdb 已满的情况下,更新操作将停止生成版本,并继续执行,但是因为所需的特定行版本不再存在,读取操作可能会失败。这将影响诸如触发器、MARS 和联机索引的操作。有关详细信息,请参阅行版本控制资源的使用情况

已提交读和快照事务的行版本控制的使用过程分为两个步骤:

  1. 将 READ_COMMITTED_SNAPSHOT 和/或 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON。

  2. 在应用程序中设置相应的事务隔离级别:

    • 当 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON 时,设置已提交读隔离级别的事务使用行版本控制。

    • 当 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON 时,事务可以设置快照隔离级别。

当 READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON 时,SQL Server 数据库引擎向使用行版本控制操作数据的每个事务分配一个事务序列号 (XSN)。事务在执行 BEGIN TRANSACTION 语句时启动。但是,事务序列号在执行 BEGIN TRANSACTION 语句后的第一次读/写操作时开始增加。事务序列号在每次分配时都增加 1。

当 READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON 时,将维护所有在数据库中执行的数据修改的逻辑副本(版本)。特定的事务每次修改行时,数据库引擎实例都存储以前提交的 tempdb 中行的图像版本。每个版本都标记有进行此更改的事务的事务序列号。已修改行的版本使用链接列表链接在一起。最新的行值始终存储在当前数据库中,并与 tempdb 中存储的版本控制行链接在一起。

注意注意

修改大型对象 (LOB) 时,只有已更改的片段才会复制到 tempdb 中的版本存储区。

行版本将保持足够长的时间,以满足在基于行版本控制的隔离级别下运行的事务的要求。数据库引擎跟踪最早的可用事务序列号,并定期删除带有比最早使用的可用序列号更低的事务序列号的所有行版本。

两个数据库选项都设置为 OFF 时,只对由触发器或 MARS 会话修改的行或由联机索引操作读取的行生成副本。这些行版本将在不再需要时被释放。后台线程会定期执行来删除陈旧的行版本。

注意注意

对于短期运行的事务,已修改行的版本将可能保存在缓冲池中,而不会写入 tempdb 数据库的磁盘文件中。如果只是临时需要副本行,它将只是简单地从缓冲池中删除而不会引发 I/O 开销。

读取数据时的行为

当在基于行版本控制的隔离下运行的事务读取数据时,读取操作不会获取正被读取的数据上的共享锁(S 锁),因此不会阻塞正在修改数据的事务。同时,由于减少了所获取的锁的数量,因此最大程度地降低了锁定资源的开销。使用行版本控制的已提交读隔离和快照隔离旨在提供副本数据的语句级或事务级读取一致性。

所有查询,包括在基于行版本控制的隔离级别下运行的事务,都在编译和执行期间获取 Sch-S(架构稳定性)锁。因此,当并发事务持有表的 Sch-M(架构修改)锁时,将阻塞查询。例如,数据定义语言 (DDL) 操作在修改表的架构信息之前获取 Sch-M 锁。查询事务,包括在基于行版本控制的隔离级别下运行的事务,都会在尝试获取 Sch-S 锁时被阻塞。相反,持有 Sch-S 锁的查询将阻塞尝试获取 Sch-M 锁的并发事务。有关锁行为的详细信息,请参阅锁兼容性(数据库引擎)

当使用快照隔离级别的事务启动时,数据库引擎实例将记录所有当前活动的事务。当快照事务读取具有版本链的行时,数据库引擎按照该链检索行,其事务序列号为:

  • 最接近但低于读取行的快照事务序列号。

  • 不在快照事务启动时活动的事务列表中。

由快照事务执行的读取操作将检索在快照事务启动时已提交的每行的最新版本。这提供了在事务启动时存在的数据的事务一致快照。

使用行版本控制的已提交读事务以大致相同的方式运行。不同之处在于选择行版本时,已提交读事务不使用其自身的事务序列号。每次启动语句时,已提交读事务将读取为数据库引擎实例发出的最新事务序列号。这是用于为该语句选择正确的行版本的事务序列号。这使已提交读事务可以查看每个语句启动时存在的数据的快照。

注意注意

即使使用行版本控制的已提交读事务提供了在语句级别上事务一致的数据视图,但此类事务生成或访问的行版本还将保留,直到事务完成时为止。

修改数据时的行为

在使用行版本控制的已提交读事务中,使用阻塞性扫描(其中读取数据值时将在数据行上采用更新锁(U 锁)完成选择要更新的行。这与不使用行版本控制的已提交读事务相同。如果数据行不符合更新标准,在该行上将释放更新锁并且将锁定下一行并对其进行扫描。

在快照隔离下运行的事务对数据修改采用乐观方法:获取数据上的锁后,才执行修改以强制应用约束。否则,直到数据修改时才获取数据上的锁。当数据行符合更新标准时,快照事务将验证未被并发事务(在快照事务开始后提交)修改的数据行。如果数据行已在快照事务以外修改,则将出现更新冲突,同时快照事务也将终止。更新冲突由数据库引擎处理,无法禁用更新冲突检测。

注意注意

当快照事务访问以下任意项目时,在快照隔离下运行的更新操作将在已提交读隔离下内部执行:

具有 FOREIGN KEY 约束的表。

在其他表的 FOREIGN KEY 约束中引用的表。

引用多个表的索引视图。

但是,即使是在这些条件下,更新操作仍将继续验证数据是否未经其他事务修改。如果数据已被其他事务修改,则快照事务将遭遇更新冲突并终止。

行为摘要

下表概括了使用行版本控制的快照隔离与已提交读隔离之间的差异。

属性

使用行版本控制的已提交读隔离级别

快照隔离级别

必须设置为 ON 以便启用所需支持的数据库选项。

READ_COMMITTED_SNAPSHOT

ALLOW_SNAPSHOT_ISOLATION

会话如何请求特定类型的行版本控制。

使用默认的已提交读隔离级别,或运行 SET TRANSACTION ISOLATION LEVEL 语句来指定 READ COMMITTED 隔离级别。这可以在事务启动后完成。

需要执行 SET TRANSACTION ISOLATION LEVEL 来在事务启动前指定 SNAPSHOT 隔离级别。

由语句读取的数据的版本。

在每条语句启动前提交的所有数据。

在每个事务启动前提交的所有数据。

如何处理更新。

从行版本恢复到实际的数据,以选择要更新的行并使用选择的数据行上的更新锁。获取要修改的实际数据行上的排他锁。没有更新冲突检测。

使用行版本选择要更新的行。尝试获取要修改的实际数据行上的排他锁,如果数据已被其他事务修改,则出现更新冲突,同时快照事务也将终止。

有更新冲突检测。

无。

集成支持。无法禁用。