一、事物隔离级别概念
一、我们为什么需要事务
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作。事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。
二、事务ACID属性
事务的特性可以定义为四种属性:ACID(atomicity,consistency,isolation,durability)。
l atomicity:原子性,在一个事务中的所有操作,相当于一个原子操作,要么全部成功,要么全部失败。
l consistency:一致性,就是在事务执行前后,对于事务本身的用意而言,数据库中的数据是保持一致的,数据库的一致性是建立在原子性的基础之上的,更多的由编码的程序员保证,最经典的案例是A,B帐号之间的转账。
在整个过程中,对于一致性的定义是AB的账户总额保持事务前后一致,而次数是通过事务的原子性以及事务定义的编码共同来实现的(如果你本意是转账,A减去50而B加上100则不符合一致性本意)。
另一方面则表示,一致性状态表示只有合法的数据才能被写入数据库,如果事务执行的某些操作,违反了数据的一致性规则,则事务会rollback到之前数据一致的状态,另一方面,如果事务执行成功,则会将数据状态从事务执行前的符合数据一致性规则的一种状态,变迁到事务执行后的另一种数据符合一致性规则的状态。(Definition: Consistency states that only valid data will be written to thedatabase. If, for some reason, a transaction is executed that violates thedatabase’s consistency rules, the entire transaction will be rolled back andthe database will be restored to a state consistent with those rules. On theother hand, if a transaction successfully executes, it will take the databasefrom one state that is consistent with the rules to another state that is alsoconsistent with the rules.)
l isolation:隔离性,事务的隔离性是指事务和事务之间的数据可见性,这也是本文需要详细介绍的地方。数据库定义了各种隔离级别,以在并发性和数据完整性中权衡。
l durability:持久性,事务完成以后,所有的数据都将持久到数据库中,不会因为其他原因而丢失。
三、 数据库锁
3.1 锁的类型
如同线程一样,数据库也用锁来保证事务之间的协同工作,在数据库中,锁分为共享锁和排他锁,顾名思义,多个共享锁可以共存,而排它锁则是独占锁,共享锁也称之为读锁,而排它锁也称之为独占锁,写锁。
l 排他锁 被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象
l 共享锁 被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁。
特别的,对共享锁: 如果两个事务对同一个资源上了共享锁,事务A 想更新该数据,那么它必须等待 事务B 释放其共享锁。
3.2 锁粒度
锁的范围和粒度可以分为表锁和行锁,其中表锁为数据库实现,而行锁则由存储引擎实现(如innodb引擎)。
3.3 *协议(Locking Protocol)
在运用锁来进行事务之间的协议时,还必须基于一定的规则,如何时加锁,何时释放锁,加锁的粒度以及等等。这些称之为*协议,不同的*协议定义了不同的隔离级别。不同的隔离级别则解决了之前事务协作锁带来的问题,以便服务在吞吐量和并发性以及事务完整性之间做出权衡。
四 事务隔离级别
针对事务协作锁带来的四大问题,于是根据不同的*协议,制定了事务间的隔离级别。其实本质上均是通过锁来干涉事务之间的执行顺序。
4.1 事务协作问题原因极其分析
l 丢失更新
原因:读锁可以共享,而记录常驻内存。
解决方式:读锁升级为独占锁,或者重复读取,cas更新。
l 脏读
执行场景(顺序):事务A在事务B更新数据和回滚过程中间读取记录。
解决方式:当事务B更新数据的时候禁止其他事务对记录进行读取。
加锁方式:事务B将写锁(独占锁)保持到事务结束。
l 不可重复读
执行场景(顺序):事务A第一次读取数据在事务B更新数据并提交之前,第二次读取数据在事务B更新记录并提交之后,导致同一次事务中多次读取同一记录不同。
解决方式:事务A在读取数据和提交事务之间不允许其他事务对数据进行修改。
加锁方式:事务A持有共享锁直至事务结束。
l 幻读
解决方式:数据库将行锁升级为表锁;
4.2 隔离级别及对应*规则
l read uncommitted(一级*规则)
表示可以接受读取未提交的数据(脏读)。*规则为:当事务修改记录时,为记录添加共享锁直至事务结束,在次过程中,其他事务可以读取和更新数据。因此此种隔离级别会带来丢失更新,脏读,重复读,幻读各种问题。
l read commited(二次*规则)
表示允许读取提交后的数据,意思是允许(不可重复读)现象,但拒绝脏读。
*规则为对应的脏读解决方案,事务将写锁(独占锁)保持到事务结束,而读取数据的时候当读取操作完毕则立即释放读锁。
执行顺序会变成:
l repeatable read(三级*协议)
表示可以重复读(意思为不允许不可重复读,真纠结)。*规则为,保持共享锁到事务结束。
但因为是行锁,无法避免幻读,另外因为事务读锁可以并存,那是否repeatable read在应对丢失更新的情况方面表现如何呢?
你会发现,因为共享锁和排他锁的释放周期均被放大,导致了资源的死锁,数据库如何解决这种死锁状况呢?如SQL Server在处理这种情况时则是引入了Update锁,当读取数据的时候为记录上获取Update锁,哪个事务先修改,则将锁升级为独占锁从而对该数据进行修改。而MySQL innodb则主要是通过两方面来处理死锁问题:
n 死锁检测;当发现死锁时,将一个事务回退(回滚拥有最少排他行级锁的事务,一种对最易回滚事务的大致估算),令一个事务获得锁完成事务,但是innodb也无法检测所有死锁的问题。
n 死锁超时;可以通过设置innodb_lock_wait_timeout来设置获取锁而等待的超时时间。
l Serialization(四级*)
直接对 事务中 所 读取 或者 更改的数据所在的表加表锁,也就是说,其他事务不能 读写 该表中的任何数据。这样所有的 脏读,不可重复读,幻读 ,都得以避免。
五、 MySql事务隔离级别设置
在mysql中,可以通过如下命令查询当前事务隔离级别:
SELECT@@global.tx_isolation; //全局
SELECT@@tx_isolation;//当前会话
设置会话级别分别为:
setglobal transaction isolation level read committed;
setsession transaction isolation level read committed;
在数据库操作中,为了有效保证并发读取数据的正确性,提出的 事务隔离级别 。
问题的提出
数据库是要被广大客户所共享访问的,那么在数据库操作过程中很可能出现以下几种不确定情况。
可归纳为以下几类:
A.丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖(A和B事务并发执行,A事务执行更新后,提交;B事务在A事务更新后,B事务结束前也做了对该行数据的更新操作,然后回滚,则两次更新操作都丢失了)。
B.脏读:一个事务读到另一个事务未提交的更新数据(A和B事务并发执行,B事务执行更新后,A事务查询B事务没有提交的数据,B事务回滚,则A事务得到的数据不是数据库中的真实数据。也就是脏数据,即和数据库中不一致的数据)。
C.不可重复读:一个事务读到另一个事务已提交的更新数据(A和B事务并发执行,A事务查询数据,然后B事务更新该数据,A再次查询该数据时,发现该数据变化了)。
D. 覆盖更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据(即A事务更新数据,然后B事务更新该数据,A事务查询发现自己更新的数据变了)。
E.虚读(幻读):一个事务读到另一个事务已提交的新插入的数据(A和B事务并发执行,A事务查询数据,B事务插入或者删除数据,A事务再次查询发现结果集中有以前没有的数据或者以前有的数据消失了)。
数据库系统提供了四种事务隔离级别供用户选择:
A.Serializable(串行化):一个事务在执行过程中完全看不到其他事务对数据库所做的更新(事务执行的时候不允许别的事务并发执行。事务串行化执行,事务只能一个接着一个地执行,而不能并发执行。)。
B.Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他其他事务对已有记录的更新。
C.Read Commited(读已提交数据):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新。
D.Read Uncommitted(读未提交数据):一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。
√: 可能出现 ×: 不会出现
|
丢失更新 |
脏读 |
非重复读 |
覆盖更新 |
幻像读 |
未提交读 |
Y |
Y |
Y |
Y |
Y |
已提交读 |
N |
N |
Y |
Y |
Y |
可重复读 |
N |
N |
N |
N |
Y |
串行化 |
N |
N |
N |
N |
N |
注意:我们讨论隔离级别的场景,主要是在多个事务并发的情况下,因此,接下来的讲解都围绕事务并发。
隔离级别
为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。数据库系统有四个隔离级别(大多数数据库默认级别为read commited)。对数据库使用何种隔离级别要审慎分析,因为
1. 维护一个最高的隔离级别虽然会防止数据的出错,但是却导致了并行度的损失,以及导致死锁出现的可能性增加。
2. 然而,降低隔离级别,却会引起一些难以发现的bug。
SERIALIZABLE(序列化)
添加范围锁(比如表锁,页锁等,关于range lock,我也没有很深入的研究),直到transaction A结束。以此阻止其它transaction B对此范围内的insert,update等操作。
幻读,脏读,不可重复读等问题都不会发生。
REPEATABLE READ(可重复读)
对于读出的记录,添加共享锁直到transaction A结束。其它transaction B对这个记录的试图修改会一直等待直到transaction A结束。
可能发生的问题:当执行一个范围查询时,可能会发生幻读。
READ COMMITTED(提交读)
在transaction A中读取数据时对记录添加共享锁,但读取结束立即释放。其它transaction B对这个记录的试图修改会一直等待直到A中的读取过程结束,而不需要整个transaction A的结束。所以,在transaction A的不同阶段对同一记录的读取结果可能是不同的。
可能发生的问题:不可重复读。
READ UNCOMMITTED(未提交读)
不添加共享锁。所以其它transaction B可以在transaction A对记录的读取过程中修改同一记录,可能会导致A读取的数据是一个被破坏的或者说不完整不正确的数据。
另外,在transaction A中可以读取到transaction B(未提交)中修改的数据。比如transaction B对R记录修改了,但未提交。此时,在transaction A中读取R记录,读出的是被B修改过的数据。
可能发生的问题:脏读。
问题
我们看到,当执行不同的隔离级别时,可能会发生各种各样不同的问题。下面对它们进行总结并举例说明。
幻读
幻读发生在当两个完全相同的查询执行时,第二次查询所返回的结果集跟第一个查询不相同。
发生的情况:没有范围锁。
例子:
请参考:http://xm-king.iteye.com/blog/770721
用户可以用SET TRANSACTION语句改变单个会话或者所有新进连接的隔离级别。它的语法如下:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
注意:默认的行为(不带session和global)是为下一个(未开始)事务设置隔离级别。如果你使用GLOBAL关键字,语句在全局对从那点开始创建的所有新连接(除了不存在的连接)设置默认事务级别。你需要SUPER权限来做这个。使用SESSION 关键字为将来在当前连接上执行的事务设置默认事务级别。 任何客户端都能*改变会话隔离级别(甚至在事务的中间),或者为下一个事务设置隔离级别。
你可以用下列语句查询全局和会话事务隔离级别:
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;
select @@tx_isolation; //查看隔离级别
set transaction isolation level read uncommitted; //设置读未提交级别
start transaction; //打开事务
rollback; //回滚
commit; //提交
set transaction isolation level read committed; //设置读提交级别
set transaction isolation level repeatable read; //设置可重复读(缺省),保证每次读的结果是一样的
set transaction isolation level serializable; //设置成序列化