在数据库操作中,为了有效保证并发读取数据的正确性,因此提出了事务隔离级别.
事务的特性
在一个事务性操作的环境下,操作具有原子性(atomic)(atomicity),一致性(consistent)(consistency),隔离性(insulation)(isolation),持久性(Duration)(durability).一般称为ACID.
原子性
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。
一致性
事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。
隔离性
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为隔离性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。防止数据丢失。
持久性
事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
因为数据库是要被广大用户所共享访问的,那么在数据库操作过程中可能会出现以下几种不确定情况.
不确定情况(问题)
更新丢失(Lost Update)
两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来
脏读(Dirt Reads)
一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚
不可重复读(Non-repeatable Reads)
- 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
- 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
有了上述的几种情况,在标准SQL规范中,定义了几个事务隔离级别(ISOLATION LEVEL),不同的隔离级别对事务的处理不同.
可参考MSDN: SET TRANSACTION ISOLATION LEVEL (Transact-SQL)
隔离级别
Transact-SQL中
在Transact-SQL中,控制Transact-SQL语句的的锁定行为和行版本控制行为.语法如下:
1 SET TRANSACTION ISOLATION LEVEL 2 { READ UNCOMMITTED 3 | READ COMMITTED 4 | REPEATABLE READ 5 | SNAPSHOT 6 | SERIALIZABLE 7 } 8 [ ; ]
一个简单示例(示例分为2个会话操作.在会话1中执行事务不提交或回滚,将会造成阻塞):
1 --创建数据库 2 CREATE DATABASE TESTDB 3 GO 4 5 USE TESTDB 6 GO 7 8 --创建表 9 CREATE TABLE STUDENT 10 ( 11 ID INT PRIMARY KEY, 12 NAME VARCHAR(50) 13 ) 14 GO 15 16 --插入数据 17 INSERT INTO STUDENT VALUES(1,'Zhang San') 18 INSERT INTO STUDENT VALUES(2,'Li Si') 19 INSERT INTO STUDENT VALUES(3,'Wang Wu') 20 GO 21 22 --执行一个事务,不提交或回滚,造成会话2的堵塞现象(因为默认的锁是Read Committed) 23 BEGIN TRAN 24 UPDATE STUDENT SET NAME='Zhang Fei' WHERE ID=1 25 --COMMIT 26 --ROLLBACK
1 --READ UNCOMMITTED --可以读取,但是会读取脏读数据 2 --指定语句可以读取已由其他事务修改但尚未提交的行。 3 4 --READ COMMITTED --不能读取 5 --指定语句不能读取已由其他事务修改但尚未提交的数据。这样可以避免脏读。 6 --其他事务可以在当前事务的各个语句之间更改数据,从而产生不可重复读取和虚拟数据。该选项是 SQL Server 的默认设置 7 8 --REPEATABLE READ --不能读取 9 --指定语句不能读取已由其他事务修改但尚未提交的行,并且指定,其他任何事务都不能在当前事务完成之前修改由当前事务读取的数据。 10 11 --SNAPSHOT --可以读取,不会读取脏读数据,但是必须要开启隔离快照才能使用,否则会异常 12 --指定事务中任何语句读取的数据都将是在事务开始时便存在的数据的事务上一致的版本。事务只能识别在其开始之前提交的数据修改。 13 --这个必须要设定开启隔离快照 (ALTER DATABASE TESTDB SET ALLOW_SNAPSHOT_ISOLATION ON) 隔离快照的ON在事务期间执行将会被堵塞 14 --在快照隔离模式下,事务中任何语句读取的 FILESTREAM 数据都将是在事务开始(而非语句开始)时便存在的数据的事务性一致版本 15 16 --SERIALIZABLE 17 --请指定下列内容: 18 --语句不能读取已由其他事务修改但尚未提交的数据。 19 --任何其他事务都不能在当前事务完成之前修改由当前事务读取的数据。 20 --在当前事务完成之前,其他事务不能使用当前事务中任何语句读取的键值插入新行。 21 22 --隔离快照 23 --ALTER DATABASE TESTDB SET ALLOW_SNAPSHOT_ISOLATION ON 24 25 --设定隔离级别 26 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 27 28 --查询(按*查询所有包括了脏读数据,如果指定查询数据不在事务更新内可正常查询) 29 SELECT * FROM STUDENT 30 31 --修改 32 UPDATE STUDENT SET NAME='Zhang SanFeng' WHERE ID=1
测试结果如下:
Level |
查询 |
修改(修改脏读数据) |
新增 |
READ UNCOMMITTED |
是(包括脏读数据) |
否 |
是 |
READ COMMITTED(默认) |
否 |
否 |
是 |
REPEATABLE READ |
否 |
否 |
是 |
SNAPSHOT |
是(无脏读数据),需要隔离快照 |
否 |
是 |
SERIALTZABLE |
否(表HOLDLOCK 锁) |
否 |
是 |
关于阻塞的现象,如果需要对表进行查询, 可以通过SET_TIMEOUT 来控制查询超时时间,这样来给事务以缓冲, 如果超时了,根据错误处理.
或者查询的时候使用WITH(NOLOCK) 表示无锁查询,但是会存在脏读数据,如下:
SELECT * FROM STUDENT WITH(NOLOCK)
READ UNCOMMITTED
指定语句可以读取已由其他事务修改但尚未提交的行。
在 READ UNCOMMITTED 级别运行的事务,不会发出共享锁来防止其他事务修改当前事务读取的数据。READ UNCOMMITTED 事务也不会被排他锁阻塞,排他锁会禁止当前事务读取其他事务已修改但尚未提交的行。设置此选项之后,可以读取未提交的修改,这种读取称为脏读。在事务结束之前,可以更改数据中的值,行也可以出现在数据集中或从数据集中消失。该选项的作用与在事务内所有 SELECT 语句中的所有表上设置 NOLOCK 相同。这是隔离级别中限制最少的级别。
在 SQL Server 中,您还可以使用下列任意一种方法,在保护事务不脏读未提交的数据修改的同时尽量减少锁定争用:
-
READ COMMITTED 隔离级别,并将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON。
-
SNAPSHOT 隔离级别。
READ COMMITTED
指定语句不能读取已由其他事务修改但尚未提交的数据。这样可以避免脏读。其他事务可以在当前事务的各个语句之间更改数据,从而产生不可重复读取和虚拟数据。该选项是 SQL Server 的默认设置。
READ COMMITTED 的行为取决于 READ_COMMITTED_SNAPSHOT 数据库选项的设置:
-
如果将 READ_COMMITTED_SNAPSHOT 设置为 OFF(默认设置),则数据库引擎会使用共享锁防止其他事务在当前事务执行读取操作期间修改行。共享锁还会阻止语句在其他事务完成之前读取由这些事务修改的行。共享锁类型确定它将于何时释放。行锁在处理下一行之前释放。页锁在读取下一页时释放,表锁在语句完成时释放。
注意:如果将 READ_COMMITTED_SNAPSHOT 设置为 ON,则数据库引擎会使用行版本控制为每个语句提供一个在事务上一致的数据快照,因为该数据在语句开始时就存在。不使用锁来防止其他事务更新数据。
快照隔离支持 FILESTREAM 数据。在快照隔离模式下,事务中任何语句读取的 FILESTREAM 数据都将是在事务开始时便存在的数据的事务性一致版本。
当 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON 时,您可以使用 READCOMMITTEDLOCK 表提示为 READ COMMITTED 隔离级别上运行的事务中的各语句请求共享锁,而不是行版本控制。
注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中仅允许存在执行 ALTER DATABASE 命令的连接。在 ALTER DATABASE 完成之前,数据库中不允许有其他打开的连接。数据库不必处于单用户模式。
REPEATABLE READ
指定语句不能读取已由其他事务修改但尚未提交的行,并且指定,其他任何事务都不能在当前事务完成之前修改由当前事务读取的数据。
对事务中的每个语句所读取的全部数据都设置了共享锁,并且该共享锁一直保持到事务完成为止。这样可以防止其他事务修改当前事务读取的任何行。其他事务可以插入与当前事务所发出语句的搜索条件相匹配的新行。如果当前事务随后重试执行该语句,它会检索新行,从而产生虚拟读取。由于共享锁一直保持到事务结束,而不是在每个语句结束时释放,因此并发级别低于默认的 READ COMMITTED 隔离级别。此选项只在必要时使用。
SNAPSHOT
指定事务中任何语句读取的数据都将是在事务开始时便存在的数据的事务上一致的版本。事务只能识别在其开始之前提交的数据修改。在当前事务中执行的语句将看不到在当前事务开始以后由其他事务所做的数据修改。其效果就好像事务中的语句获得了已提交数据的快照,因为该数据在事务开始时就存在。除非正在恢复数据库,否则 SNAPSHOT 事务不会在读取数据时请求锁。读取数据的 SNAPSHOT 事务不会阻止其他事务写入数据。写入数据的事务也不会阻止 SNAPSHOT 事务读取数据。
在数据库恢复的回滚阶段,如果尝试读取由其他正在回滚的事务锁定的数据,则 SNAPSHOT 事务将请求一个锁。在事务完成回滚之前,SNAPSHOT 事务会一直被阻塞。当事务取得授权之后,便会立即释放锁。
必须将 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON,才能开始一个使用 SNAPSHOT 隔离级别的事务。
如果使用 SNAPSHOT 隔离级别的事务访问多个数据库中的数据,则必须在每个数据库中将 ALLOW_SNAPSHOT_ISOLATION 都设置为 ON。不能将通过其他隔离级别开始的事务设置为 SNAPSHOT 隔离级别,否则将导致事务中止。如果一个事务在 SNAPSHOT 隔离级别开始,则可以将它更改为另一个隔离级别,然后再返回 SNAPSHOT。事务在第一次访问数据时启动。
在 SNAPSHOT 隔离级别下运行的事务可以查看由该事务所做的更改。例如,如果事务对表执行 UPDATE,然后对同一个表发出 SELECT 语句,则修改后的数据将包含在结果集中。
注意:在快照隔离模式下,事务中任何语句读取的 FILESTREAM 数据都将是在事务开始(而非语句开始)时便存在的数据的事务性一致版本。
SERIALIZABLE
请指定下列内容:
-
语句不能读取已由其他事务修改但尚未提交的数据。
-
任何其他事务都不能在当前事务完成之前修改由当前事务读取的数据。
-
在当前事务完成之前,其他事务不能使用当前事务中任何语句读取的键值插入新行。
范围锁处于与事务中执行的每个语句的搜索条件相匹配的键值范围之内。这样可以阻止其他事务更新或插入任何行,从而限定当前事务所执行的任何语句。这意味着如果再次执行事务中的任何语句,则这些语句便会读取同一组行。在事务完成之前将一直保持范围锁。这是限制最多的隔离级别,因为它锁定了键的整个范围,并在事务完成之前一直保持范围锁。因为并发级别较低,所以应只在必要时才使用该选项。该选项的作用与在事务内所有 SELECT 语句中的所有表上设置 HOLDLOCK 相同。
在.NET Framework 数据提供程序中
这里指的是System.Data下的IsolationLevel枚举,如下图: 见MSDN:IsolationLevel 枚举