3、可重复读取(相当于(HOLDLOCK)):第三级别
MySQL的默认事务隔离级别。
可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻读数据。
这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
缺点:会产生幻读。
问题解读:股市忽涨忽跌,老王焦虑不安,按捺不住,想把持有的多种股票全部抛掉。与此同时,老王老婆听信专家所言,使用老王的账号买了某只神股。
老王抛掉所有股票后,查看自己的持股,猛然发现自己居然还持有一只股票,瞬间觉得一脸懵逼,这就是幻读导致。
解决方案:采用更高级的隔离机制,序列化。
4、序列化(这是最高的隔离级别):第四级别
序列化(Serializable):提供严格的事务隔离。
它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。
仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
缺点:可以解决并发事务的所有问题。但是效率地下,消耗数据库性能,一般不使用。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。
尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
5、快照
(1)SNAPSHOT 在SNAPSHOT隔离级别下,当读取数据时可以保证操作读取的行是事务开始时可用的最后提交版本。
(2)同时SNAPSHOT隔离级别也满足前面的已提交读,可重复读,不幻读;该隔离级别实用的不是共享锁,而是行版本控制。
6、已提交读快照
READ COMMITTED SNAPSHOT也是基于行版本控制,但是READ COMMITTED SNAPSHOT的隔离级别是读操作之前的最后已提交版本,
而不是事务前的已提交版本,有点类似前面的READ COMMITTED能保证已提交读,但是不能保证可重复读,不能避免幻读,
但是又比 READ COMMITTED隔离级别多出了不需要获取共享锁就可以读取数据。
事务的隔离级别设置示例
SQL Server通过在锁资源上使用不同类型的锁来隔离事务。
为了开发安全的事务,定义事务内容以及应在何种情况下回滚至关重要,定义如何以及在多长时间内在事务中保持锁定也同等重要。
这由隔离级别决定,应用不同的隔离级别,SQL Server赋予开发者一种能力,让他们为每一个单独事务定义与其他事务的隔离程度。事务隔离级别的定义如下:
获取事务隔离级别(isolation level)
DBCC USEROPTIONS
1、未提交读取
新建回话并将学生编号为100001的成绩+1;
BEGIN TRANSACTION UPDATE a_StudentsScore SET C#=C#+1 WHERE Number='100001' SELECT * FROM a_StudentsScore WHERE Number='100001'
然后执行错误的回话,进行回滚
BEGIN TRANSACTION UPDATE a_StudentsScore SET C#=C#+'' WHERE Number='100001'
然后在查询数据
--首先不添加隔离级别,默认是READ COMMITTED,由于数据之前的更新操作使用了排他锁,所以查询一直在等待锁释放*/ SELECT * FROM a_StudentsScore WHERE Number='100001' ---将查询的隔离级别设置为READ UNCOMMITTED允许未提交读,读操作之前不请求共享锁。 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED SELECT * FROM a_StudentsScore WHERE Number='100001' --当然也可以使用表隔离,效果是一样的 SELECT * FROM a_StudentsScore WITH (NOLOCK) WHERE Number='100001'
我们发现执行两个事务回话,第一个执行成了,但是第二个执行失败了进行回滚,最后查询的数据是第一个执行前的数据,没有任何改变。
2、提交读取
新建回话1并将学生编号为100001的成绩+1,此时回话的排他锁锁住了学生编号为100001的成绩
BEGIN TRANSACTION UPDATE a_StudentsScore SET C#=C#+1 WHERE Number='100001' SELECT * FROM a_StudentsScore WHERE Number='100001'
在回话2中执行查询,将隔离级别设置为READ COMMITTED
SET TRANSACTION ISOLATION LEVEL READ COMMITTED SELECT * FROM a_StudentsScore WHERE Number='100001' --由于READ COMMITTED需要申请获得共享锁,而锁与回话1的排他锁冲突,回话被堵塞, --在回话1中执行事务提交 COMMIT TRANSACTION --由于回话1事务提交,释放了学生100001的排他锁,此时回话2申请共享锁成功查到学生100001的C#成绩为修改后的成绩81,READ COMMITTED由于是已提交读隔离级别,所以不会读脏数据.
重置数据
UPDATE a_StudentsScore SET C#=80 WHERE Number='100001'
注意:但是由于READ COMMITTED读操作一完成就立即释放共享锁,读操作不会在一个事务过程中保持共享锁,
也就是说在一个事务的的两个查询过程之间有另一个回话对数据资源进行了更改,会导致一个事务的两次查询得到的结果不一致,这种现象称之为不可重复读.
3、可重复读取
在回话1中查询学生编号为100001的成绩,,将回话级别设置为REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT * FROM a_StudentsScore WHERE Number='100001'
新建回话2修改学生编号为100001的成绩
UPDATE a_StudentsScore SET C#=C#+1 WHERE Number='100001' ---由于回话1的隔离级别REPEATABLE READ申请的共享锁一直要保持到事务结束,所以回话2无法获取排他锁,处于等待状态
在回话1中执行下面语句,然后提交事务
SELECT * FROM a_StudentsScore WHERE Number='100001' COMMIT TRANSACTION
回话1的两次查询得到的结果一致,前面的两个隔离级别无法得到一致的数据,此时事务已提交同时释放共享锁,回话2申请排他锁成功,对行执行更新
REPEATABLE READ隔离级别保证一个事务中的两次查询到的结果一致,同时保证了丢失更新
丢失更新:两个事务同时读取了同一个值然后基于最初的值进行计算,接着再更新,就会导致两个事务的更新相互覆盖。
例如酒店订房例子,两个人同时预定同一酒店的房间,首先两个人同时查询到还有一间房间可以预定,然后两个人同时提交预定操作,事务1执行number=1-0,同时事务2也执行number=1-0最后修改number=0,这就导致两个人其中一个人的操作被另一个人所覆盖,REPEATABLE READ隔离级别就能避免这种丢失更新的现象,当事务1查询房间时事务就一直保持共享锁直到事务提交,而不是像前面的几个隔离级别查询完就是否共享锁,就能避免其他事务获取排他锁。
4、序列化
SERIALIZABLE(可序列化),对于前面的REPEATABLE READ能保证事务可重复读,但是事务只锁定查询第一次运行时获取的数据资源(数据行),
而不能锁定查询结果之外的行,就是原本不存在于数据表中的数据。因此在一个事务中当第一个查询和第二个查询过程之间,有其他事务执行插入操作且插入数据满足第一次查询读取过滤的条件时,
那么在第二次查询的结果中就会存在这些新插入的数据,使两次查询结果不一致,这种读操作称之为幻读。
为了避免幻读需要将隔离级别设置为SERIALIZABLE。
在回话1中执行查询操作,并将事务隔离级别设置为REPEATABLE READ(先测试一下前面更低级别的隔离)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT * FROM a_StudentsScore WHERE Number='100001'
在回话2中执行修改操作
UPDATE a_StudentsScore SET C#=C#+1 WHERE Number='100001'
返回回话1重新执行查询操作并提交事务
SELECT * FROM a_StudentsScore WHERE Number='100001' COMMIT TRANSACTION
结果回话1中第二次查询到的数据包含了回话2新修改的数据,两次查询结果不一致(验证之前的隔离级别不能保证幻读)
接下来将回话级别设置为SERIALIZABLE,在回话1中执行查询操作,并将事务隔离级别设置为SERIALIZABLE。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRANSACTION SELECT * FROM a_StudentsScore WHERE Number='100001'
在回话2中执行修改操作
UPDATE a_StudentsScore SET C#=C#+1 WHERE Number='100001'
返回回话1重新执行查询操作并提交事务
SELECT * FROM a_StudentsScore WHERE Number='100001' COMMIT TRANSACTION
两次执行的查询结果相同
重置所有打开回话的默认隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
5、快照
SNAPSHOT 在SNAPSHOT隔离级别下,当读取数据时可以保证操作读取的行是事务开始时可用的最后提交版本
同时SNAPSHOT隔离级别也满足前面的已提交读,可重复读,不幻读;该隔离级别实用的不是共享锁,而是行版本控制
使用SNAPSHOT隔离级别首先需要在数据库级别上设置相关选项
在打开的所有查询窗口中执行以下操作
ALTER DATABASE TEST SET ALLOW_SNAPSHOT_ISOLATION ON;
--在回话1中打开事务,将学生100001的成绩加1,并查询跟新后的成绩 BEGIN TRANSACTION UPDATE a_StudentsScore SET C#=C#+1 WHERE Number='100001' SELECT * FROM a_StudentsScore WHERE Number='100001' ---查询到更新后的成绩为81 ---在回话2中将隔离级别设置为SNAPSHOT,并打开事务(此时查询也不会因为回话1的排他锁而等待,依然可以查询到数据) SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN TRANSACTION SELECT * FROM a_StudentsScore WHERE Number='100001' ---查询到的结果还是回话1修改前的成绩,由于回话1在默认的READ COMMITTED隔离级别下运行,SQL SERVER必须在更新前把行的一个副本复制到TEMPDB数据库中 --在SNAPSHOT级别启动事务会请求行版本 ---现在在回话1中执行提交事务,此时学生100001的成绩为81 COMMIT TRANSACTION ---再次在回话2中查询学生100001的成绩并提交事务,结果还是80,因为事务要保证两次查询的结果相同 SELECT * FROM a_StudentsScore WHERE Number='100001' COMMIT TRANSACTION ---此时如果在回话2中重新打开一个事务,查询到的学生100001的成绩为81 BEGIN TRANSACTION SELECT * FROM a_StudentsScore WHERE Number='100001' COMMIT TRANSACTION --SNAPSHOT隔离级别保证操作读取的行是事务开始时可用的最后已提交版本,由于回话1的事务未提交,所以学生100001的最后提交版本还是修改前的成绩80, --所以回话2读取到的成绩是回话2事务开始前的已提交版本成绩80,当回话1提交事务后,回话2重新新建一个事务此时事务开启前的成绩已经是81了, --所以查询到的成绩是81,同时SNAPSHOT隔离级别还能保证SERIALIZABLE的隔离级别
6、已提交读快照
READ COMMITTED SNAPSHOT也是基于行版本控制,但是READ COMMITTED SNAPSHOT的隔离级别是读操作之前的最后已提交版本,而不是事务前的已提交版本,
有点类似前面的READ COMMITTED能保证已提交读,但是不能保证可重复读,不能避免幻读,但是又比 READ COMMITTED隔离级别多出了不需要获取共享锁就可以读取数据
要启用READ COMMITTED SNAPSHOT隔离级别同样需要修改数据库选项,
在回话1,回话2中执行以下操作(执行下面的操作当前连接必须是数据库的唯一连接,可以通过查询已连接当前数据库的进程,然后KILL掉那些进程,然后再执行该操作,否则可能无法执行成功)。
--在回话1中打开事务,将学生100001的成绩加1,并查询跟新后的成绩,并保持事务一直处于打开状态 UPDATE a_StudentsScore SET C#=C#+1 WHERE Number='100001' --查询到的成绩是81 SELECT * FROM a_StudentsScore WHERE Number='100001' --在回话2中打开事务查询学生100001并一直保持事务处于打开状态(此时由于回话1还未提交事务,所以回话2中查询到的还是回话1执行事务之前保存的行版本) BEGIN TRANSACTION SELECT * FROM a_StudentsScore WHERE Number='100001' --查询到的成绩还是80 --在回话1中提交事务 COMMIT TRANSACTION --在回话2中再次执行查询学生100001的成绩,并提交事务 SELECT * FROM a_StudentsScore WHERE Number='100001' COMMIT TRANSACTION --此时的成绩为回话1修改后的成绩81,而不是事务之前已提交版本的成绩,也就是READ COMMITTED SNAPSHOT隔离级别在同一事务中两次查询的结果不一致.
关闭所有连接,然后打开一个新的连接,禁用之前设置的数据库快照隔离级别选项。
ALTER DATABASE TEST SET ALLOW_SNAPSHOT_ISOLATION OFF; ALTER DATABASE TEST SET READ_COMMITTED_SNAPSHOT OFF;
参考文献1:百度百科:事务
参考文献2:百度百科:事务隔离级别