SQL Server 并发控制 第三篇:隔离级别和行版本(2)
SQL Server 并发控制 第三篇:隔离级别和行版本(2)
隔离级别定义事务处理数据读取操作的隔离程度,隔离级别控制读操作的行为。在乐观并发模式下,使用行版本化技术,当对数据进行更新时,都会在tempdb中存储该数据行的原始副本,术语叫作行版本(Row Version),把tempdb中存储行版本的空间叫做版本库。在修改操作发生时,SQL Server 创建一个Row Version,将原始数据复制到版本库,Row Version是在修改操作之前已提交的数据。在数据更新期间,如果有其他读操作要访问该数据,那么它将读取到数据的副本,并且不会阻塞写操作。当写操作完成时,释放行版本。
总结,在乐观并发模式下,使用行版本来保证事务的ACID属性,当读操作引用被其他事务更新,但尚未提交的数据时:
- 对于写操作,对正在更新的数据进行备份,把备份存储到tempdb中。
- 对于读操作,从tempdb中读取行版本,读取在写操作之前存储的副本。
一,启用基于快照的隔离级别
在乐观并发模式下,有两个基于快照的隔离级别,都使用行版本来维护读操作的一致性:
- snapshot 隔离级别,简称SI,实现事务级别的数据一致性,在同一个事务中,读取到的数据是一致的,其行为和SERIALIZABLE隔离级别相同。
- read committed snapshot 隔离级别,简称RCSI,实现语句级别的数据一致性,在同一事务中,可能读取到的数据是不一致的。
1,启用RCSI
在默认的隔离级别Read Committed下,使事务读取Row Versioning数据,只需要把数据库选项READ_COMMITTED_SNAPSHOT设置为ON:
ALTER DATABASE CURRENT SET READ_COMMITTED_SNAPSHOT ON WITH NO_WAIT;
当启用READ_COMMITTED_SNAPSHOT选项,意味着把数据库的默认隔离级别设置为RCSI。如果禁用READ_COMMITTED_SNAPSHOT选项,意味着数据库的默认隔离级别是悲观并发模式下的READ COMMITTED隔离级别。
在RCSI隔离级别下,事务有两个特性:
- 事务使用行版本(Row version)代替加锁,读操作不会阻塞其他事务的写操作;
- RCSI隔离级别保证语句级别的事务一致性,查询语句只能读取在该语句执行时已经提交的数据,如果在该语句执行时数据更新尚未提交,该语句读取不到。
2,启用SI
使用快照隔离级别,必须分两步来设置,第一步,设置数据库选项 ALLOW_SNAPSHOT_ISOLATION 为 ON,但是该选项没有改变Session-Level的事务隔离级别,第二步,需要修改Session-Level的事务隔离级别为SNAPSHOT,才能使用行版本数据。
step1,设置数据库选项,允许快照隔离级别
ALTER DATABASE CURRENT SET ALLOW_SNAPSHOT_ISOLATION ON;
step2,把会话的隔离级别设置为SNAPSHOT
把当前Session的隔离级别设置为Snapshot,事务才能访问Row Versioning的数据:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
如果不把会话的隔离级别设置为SNAPSHOT,会话的隔离级别是悲观模式下的READ COMMITTED。
二,基于快照的隔离级别
SQL Server 数据库默认的事务隔离级别是Read Committed,会话默认的隔离级别是数据库默认的隔离级别,即是Read Committed。但是会话默认的隔离级别受到数据库选项 READ_COMMITTED_SNAPSHOT 的影响,该选项影响Read Committed 隔离级别是使用行版本控制事务的读操作,还是使用加共享锁来控制事务的读操作。虽然用户不能修改数据库默认的隔离级别,但是用户可以修改会话默认的隔离级别。
在默认的Read Committed隔离级别下,事务不能读取被其他事务修改,但尚未提交的数据,即只能读取已提交更新的数据:
- 设置选项READ_COMMITTED_SNAPSHOT为OFF(默认设置),数据库引擎使用Shared Lock阻止其他事务修改当前事务正在读取的数据;当读取被其他事务修改,但尚未提交更新的数据行时,该读操作将被阻塞。
- 设置选项READ_COMMITTED_SNAPSHOT为ON,数据库引擎使用行版本化(Row Versioning)的数据实现语句级别的一致性,在执行读操作时,事务不会申请共享锁,不会阻塞其他事务的写操作,但只能读取已提交的数据。
1,READ COMMITTED Snapshot隔离级别
READ COMMITTED Snapshot隔离级别(简称RCSI)保证语句级别的读一致性。当数据库选项 READ_COMMITTED_SNAPSHOT 设置为ON,Read Committed隔离级别使用Row Version提供语句级别(Statement-Level)的读一致性,即RCSI隔离级别。
在RCSI下,当一个事务开始运行在RCSI级别下,语句只会看到语句开始时的快照,当重新读取同一个数据时,该数据可能被其他写操作修改,也即是说,在同一个事务中,对同一个数据重复读取,得到的值可能不同。
2,SANPSHOT隔离级别
另外一个基于快照的隔离级别是SANPSHOT隔离级别(简称SI),Snapshot隔离级别使用Row Version 实现事务级别(Transaction-Level)的读一致性。在当前事务开始时,任何读操作,都基于相同的数据snapshot。当读取被其他事务修改的数据行时,读操作只会看到事务开始时的快照,在同一个事务中,对取同一个数据重复读取,得到的值是相同的。
在Snapshot隔离级别下,事务在修改任何数据之前,先将原始数据行复制到tempdb,创建数据行的一个原始版本(Row Version),注意,SQL Server只会复制被修改的数据行,对于未修改的数据行,不会保存行版本数据。后续其他事务的读操作都去读该复制的行版本。在Snapshot隔离级别下,读写操作不会互相阻塞,使用行版本控制能够提高事务的并发性,但是有一个明显的缺点,虽然用户读到的不是脏数据,但是数据可能正在被修改,很快就要过期。如果根据这个过期的数据做数据修改,可能会产生逻辑错误。
3,RCSI和SI的异同
相同点:在snapshot 和 read committed snpshot隔离级别下,事务读取的数据都是已提交的;
不同点:RCSI保证语句级别的读一致性,SI保证事务级别的读一致性:
- 事务级别的读一致性是指:在事务开始,到事务提交期间,该事务持有数据的一个快照。如果在该事务活动期间,其他事务更新表数据,该事务只会读取快照数据,不会读取到被其他事务更新的数据值;
- 语句级别的读一致性是指:单个语句(single statement)看到的数据是一致性的;在当前事务活动期间,事务中的语句能够读取到被其他事务提交更新的数据值;例如,在语句stmt1执行时,事务没有提交更新,stmt1看到Reader1的值是2;当语句stmt2执行时,事务提交更新,stmt2看到Reader2的值是3;
三,引用徐海蔚老师的例子,测试隔离级别的行为
snapshot隔离级别不会阻塞其他事务的写操作,该隔离级别忽略数据的修改操作,只读取row versioning的数据,就是说,读取到的是数据修改之前的版本,当snapshot事务尝试修改由其他事务修改的数据时,产生更新冲突,写操作异常终止。
read committed snapshot隔离级别,读取行版本化的已提交数据:
- 当其他事务未提交更新时,读取行版本化的数据,即读取修改之前的数据值;
- 当其他事务提交数据更新后,读取修改后数据值;
- 由于该隔离级别不会申请共享锁,因此不会阻塞其他事务的更新操作;
- 能够更新由其他事务修改的数据;
四,Snapshot隔离级别
在SNAPSHOT隔离级别下,任何写操作都会将更新之前的数据行保存到tempdb中,读取操作要么从原始数据表中读取数据,要么从tempdb中读取行版本化的数据。Snapshot隔离级别指定:在一个事务中,任何语句读取的数据,是事务一致性的版本。事务一致性是指在事务开始时,在表级别创建数据快照,只能识别其他事务已提交的数据更新。在事务开始之后,当前事务不会识别其他事务执行的数据更新。Sanpshot隔离级别实现事务级别的数据一致性。SQL Server 使用tempdb来存储行版本化(row versioning)的数据,如果数据更新较多,存储的行版本太多,会导致tempdb成为系统瓶颈。
1,在Snapshot隔离级别下,更新操作创建Row Version
一旦启用Snapshot隔离级别,在事务中执行更新操作时,SQL Server将被更新的数据行的原始版本存储在tempdb中,即在tempdb中保存数据行的原始数据,因此,读操作获取取行版本的数据,都是数据被更新之前的值。
2,Snapshot隔离实现事务一致性
Snapshot隔离级别实现事务级别的数据一致性,这意味着,在单个事务中的所有查询语句,看到的是相同版本的数据。在Snapshot隔离级别下,事务在读取数据不需要加行级锁或页级锁,读写操作互不阻塞。
快照隔离级别使得读操作不需要对基础数据行或数据页上施加共享锁,这使其他写事务不会被先前未完成的读事务阻止。修改数据的事务不会阻止读取数据的事务,并且读取数据的事务不会阻止写入数据的事务,这种非阻塞行为显着降低了复杂事务发生死锁的可能性。
3,Snapshot 使用乐观并发模式
Snapshot隔离级别使用乐观并发模式,如果一个Snapshot 事务尝试去提交数据行的更新,但是该数据行已经被其他事务修改,并且修改的时间早于当前事务开始的时间,那么SQL Server将当前事务作为失败者,并回滚其事务操作。乐观并发模式用于冲突较少的环境中,如果应用程序在更新数据时经常发生冲突,Snapshot隔离级别可能不是最好的选择。
参考文档:
Snapshot Isolation in SQL Server
Isolation Levels in the Database Engine
SQL SERVER – Difference Between Read Committed Snapshot and Snapshot Isolation Level