Sql Server中的事务与事务隔离级别
事务是数据库进行并发控制非常重要的机制,通过本文来系统的了解一下事务到底是怎么回事。
1、什么是事务?
事务是作为单个逻辑工作单元执行的一系列操作,它由一条或者一组语句组成,它们么全部成功,要么全部失败。
举个例子,比如在12306订火车票,要么你订票成功,余票显示就减少一张;要么你订票失败,余票显示还是那么多。不允许出现你订票成功了,余票却没有减少的情况。那么这种购票和余票减少的两个不同的操作必须放在一起,成为一个完成的逻辑,这样就构成了一个事务。
2、事务有哪些特性?
原子性(Atomicity): 原子性是指一个是事务中包含的一条语句或者多条语句构成了一个完整的工作单元,那么这个工作单元要么一起提交执行全部成功,要么执行全部失败;
一致性(Consistency):可以理解为数据的完整性,它用来保证数据从一个一致性的状态变成另外一个一致性的状态,比如账户A给账户B转账100元,那么账户A应该减少100元,账户B增加100元,但是两人钱数总和还是没变的;
隔离性(Isolation):一个事务的所做的修改不能被其他的事务影响,读取到的数据状态要么是事务开始前的,要么是事务结束后的;
持久性(Durability):事务一旦对数据的操作完成后,数据修改就已经完成了。
3、事务的分类?
事务有三种常见的类型:
自动提交事务:sql server 的默认事务类型,每条单独的SQL语句都是单独的一个事务,语句执行完毕自动提交,错误则自动回滚;
显示事务:显示的声明事务的开始(BEGIN TRANSACTION)和结束(COMMIT TRANSACTIOIN或ROLLBACK TRANSACTION);
隐式事务:使用 SET IMPLICIT_TRANSACTIONS ON语句启动隐式事务,使用SET IMPLICIT_TRANSACTIONS OFF 关闭隐式事务,但不会像自动模式那样自动执行ROLLBACK或COMMIT语句,隐式事务必须显示结束(COMMIT或ROLLBACK);
4、事务隔离级别
隔离级别是用来限制一个事务中正在读取或被修改的数据免于被其他事务修改的程度。
理论上每个事务和其他的事务都应该完全隔离开来。然而出于性能和可行性的原因,实践中几乎不可能做到的。在并发环境下如果没有锁和隔离级别,可能会发生以下四种情况:
脏读:在这种情况下,一个事务能够读取另一个事务正在修改且未提交的数据,那么另一个事务如果发生回滚操作,将导致第一个事务读取到的数据和实际的数据不一致;
丢失更新:这种情况下,事务没有隔离。多个事务能够读取同一份数据并且修改它。最后对数据集做出修改的事务将胜出,而其他的事务所做的修改都失效;
不可重复读:两个事务读取数据,但是在第二个事务读取前,另一个事务修改了该数据,因此两次读取的数据不一致;
幻读:这种情况和不可重复读类似,不同的是,两个事务读取一个范围的数据,但是在第二个事务读取之前,另一个事务新增了一条数据,导致两次读取的结果不同。
在了解了并发情况下出现的上述问题后,就可以进一步理解隔离级别的概念,通俗一点讲就是:你希望以何种方式将并发的事务隔离开来, 隔离到什么程度?比如允许脏读,等。隔离级别越高,读取脏数据或者造成数据不一致的情况就越少,但是在高并发系统中的性能降低就越严重。
Sql Server 2008支持6种隔离级别:
- 未提交读(Read Uncommited)
- 已提交读(Read Commited)
- 可重复读(Repeatable Read)
- 序列化(Serializable)
- 快照(Snapshot)
- 已提交读快照(Read Commited Snapshot)
下面我们通过代码来演示各个事务隔离级别的表现:
未提交读(Read Uncommited)
隔离级别最低,允许一个事务读取其他事务修改但未提交的数据,也就是会产生“脏读”的问题,它的作用和SELECT语句在对象表上设置(NOLOCK)相同。
打开两个查询窗口,下面第一个表示事务A,第二个表示事务,事务A保持默认隔离级别,事务B设置为Read Uncommitted,先执行事务A,接着执行事务B;
语法:SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
DBCC USEROPTIONS ---查看当前设置
事务A:
事务B:
从图中可以看出,事务B对同一条数据读取了两次,但很明显第一次读到的数据是“脏数据”
已提交读(Read Committed)
这是Sql Server的默认隔离级别,一个事务不允许读取另一个事务未提交的数据,Read Committed可以有效的防止“脏读”的出现,但是能够读取由其他事务修改并提交的数据,也就是说,有可能会出现“不可重复读”和“幻读”的问题。
接着上一个例子,我们把事务B的隔离级别设置为Read Committed,来看一下执行情况,还是先执行事务A,再接着执行事务 B;
事务A:
事务B:
在当前隔离级别下,当事务B读取数据时,事务B必须等待事务A结束以后才能读取,但是也有可能会出现其他问题,请看例子。先执行事务A,接着执行事务B。
从上图中可以看的出来,事务A读第一次查询后,事务B对数据进行了修改,导致事务A两次查询不一致,因此出现了“不可重复读”的情况,那么怎么避免呢,接着往下看
可重复读(Repeatable Read)
一个事务读取的数据在未结束之前,不能被其他事务修改,这个隔离级别可以解决“不可重复读”的问题
通过结果我们可以看出事务A两次读取的数据时一致的 ,那么如果把事务B的修改换成插入呢,修改上面的示例:
通过运行结果我们发现,事务A两次读取的结果集不一样了,这就是幻读。那么我们继续看下一个隔离级别
序列化(SERIALIZABLE)
这是最高级别的隔离,它会锁定一个范围的数据,从而阻止其他事务修改或新增这个范围的数据,修改上面的例子,依然是先执行事务A,在执行事务B
从执行结果可以看出,事务A两次读取数据的结果是一致的,事务B明显是等待事务A结束后才执行完成的,虽然序列化隔离级别更高,也可以很好的避免并发产生的问题,但是同时也降低了数据的可用性。
另外两个隔离级别是基于行版本的隔离级别
快照(SNAPSHOT)
通过在事务开始前在tempdb中创建一份数据库的虚拟快照,此后它只允许事务访问该数据库的虚拟快照,但是如果当前快照事务修改已经由其他事务修改的数据,将导致错误并终止,默认情况下SNAPSHOT隔离级别在数据库中是不启用的,那么需要使用ALTER DATABASE test SET ALLOW_SNAPSHOT_ISOLATION ON启用
只有当数据库中启用SNAPSHOT事务隔离级别的开关打开后,才能使用它。打开此开关将告知数据库去设置版本化环境。理解这一点很重要,因为,一旦版本化开启,数据库会有维护版本化的开销,无论是否有事务正在使用SNAPSHOT事务隔离级别。
提交读快照(READ COMMITTED SNAPSHOT)
提交读快照和快照隔离级别是类似的,不同的是:
- 通过ALTER DATABASE Test SET READ_COMMITTED_SNAPSHOT ON 来开启数据库提交读快照,运行这句话的时候不能有其他连接到当前DB;
- 已提交读启用后,READ_COMMITTED事务会通过使用行版本提供数据读取的一致性;
- 在其他事务修改提交后可以读取到修改的数据,而快照事务在当前事务未重新开始前读取的都是快照数据;
- 能够更新由其他事务修改的数据,而快照事务不能;
针对这两种隔离级别的测试,有兴趣的话可以自己试一下。
五、总结
隔离级别 | 解决并发问题 | 存在的并发问题 | 说明 |
READ UNCOMMITED | 不适用于并发场合 | 脏读、不可重复读、幻读 | 隔离级别最低,允许一个事务读取其他事务修改但未提交的数据 |
READ COMMITED | 脏读 | 丢失更新、不可重复读、幻读 | 这是Sql Server的默认隔离级别,一个事务不允许读取另一个事务未提交的数据 |
REPEATABLE READ | 不可重复读 | 幻读、死锁 | 一个事务读取的数据在未结束之前,不能被其他事务修改 |
SERIALIZABLE | 幻读 | 数据可用性降低、死锁 | 这是最高级别的隔离,它会锁定一个范围的数据,从而阻止其他事务修改或新增这个范围的数据 |
SNAPSHOT | 上述所有并发问题 | 事务访问的是虚拟快照,其他事务Commited的数据对当前事务仍然不可见,也不允许Update被其他事务Update的数据 | 快照事务隔离级别默认是不启用的,是基于行版本的事务控制 |
READ COMMITTED SNAPSHOT | 上述所有并发问题 | 无 | 它可以读取被其他事务提交的数据,也可以修改数据 |