浅入浅出MySQL事务
在开发Web应用时,经常会用到InnoDB事务的特性,在一些涉及到金钱的业务上,事务可以保证资金流水不出错,事务可以分为很多种,有扁平事务、链事务、分布式事务等,这里只讨论最简单,也最常用的扁平事务,我们经常会提到事务的ACID
特性,以下是我对ACID
的理解和总结。
- 原子性
- 一致性
- 隔离性
- 持久性
原子性
原子性指的是对于一系列增删改查的操作,要么全部执行,要么全部不执行,事务可通过start transaction
或者begin
语句显示的开启,commit
用于显示的提交,例如:
start transaction
update `user` set name='haha' where uid=1
insert into `user` (name) values ('小马')
commit
对于以上两部操作,若成功则都成功,若由于某些原因第二条语句执行出错,则可以执行rollback
语句,这些一般是由程序语言来控制。
一致性
一致性和原子性有很多相似的地方,我的理解是,一致性的特点是依赖于原子性实现的,什么意思呢?比如说A给B汇款1000元,这个状态分为两步即:
- 从A的账户扣1000元
- 再往B的账户+1000元
这两个步骤,对于事务的原子性来说,指要么都成功,要么都失败,原子性关注的是状态,而一致性关注的是最终的金额是否一致,即1000元不会丢失,可能理解起来会有点歧义,它们之间有相似的地方,一致性依赖于原子性实现。参考 事务隔离级别浅析。
隔离性
隔离性指的是事务在提交之前,对其它事务是不可见的,每个事务对对象的操作相对其它事务都是相互分离的,这个特性依靠锁机制来实现。
持久性
持久性指的是事务一旦提交,其结果是永久性的,即使发生宕机等故障,数据库也能将数据恢复。
事务的隔离级别
在数据库操作中,为了解决并发数据读写时数据的正确性问题,提出了事务的隔离级别。数据库的锁也是为了构建这些隔离级别而存在的。下面是SQL标准定义的四个隔离级别,以及可能发生脏读、不可重复读、幻读的可能性,隔离级别从上往下一次递增。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
脏读、不可重复读、幻读
要理解事务的隔离级别,首先要理解脏读、不可重复读和幻读是什么意思,下面通过几个例子来演示一下。
- 脏读
事务A | 事务B |
---|---|
START TRANSACTION |
START TRANSACTION |
update user set name='小马' where uid=1 |
|
select * from user where uid=1 |
|
commit |
rollback |
在Read uncommitted
隔离级别下,A事务和B事务同时开启,B事务对uid=1的这行数据做了修改操作,没有提交,但是在事务A里面已经能被读取到,而此时事务B执行了rollback
回滚操作,也就是说A读到的是一行不存在的数据,这种情况被称为脏读
,只有在未提交读
的隔离级别下才会发生这种情况。
- 可重复读
事务A | 事务B |
---|---|
START TRANSACTION |
START TRANSACTION |
select * from user where uid = 1 |
... |
... | update user set name='小马' where uid=1 |
... | commit |
select * from user where uid = 1 |
... |
不可重复读指的是在一个事务中,执行两次查询操作,两次结果是不一样的,称为不可重复读,可重复读则相反,同一个事务内,两次读到的数据一致。根据上面的例子,事务A和事务B同时开启,第一次A事务通过select
语句查询uid=1的这行数据,这时候B修改了这行数据并且执行了提交操作,第二次A事务再通过select
语句查询这行数据的时候,读取到的结果就不一样了,所以称为不可重复读,这种情况在Read committed
隔离级别下存在,Repeatable read
级别则实现了可重复读。
- 幻读
事务A | 事务B |
---|---|
START TRANSACTION |
START TRANSACTION |
select count(*) from user |
... |
... | insert into user (name) values ('mike') |
... | commit |
select count(*) from user |
... |
幻读和不可重复读有一些相似的地方,不可重复读针对的是修改删除操作,这两种操作可通过对数据行增加一个排它锁来解决,但是insert
操作不一样,你没有办法锁住一条尚未存在的数据,理论上在Repeatable read
隔离级别只解决了不可重复读问题,没有解决幻读问题,RR级别也是innodb默认的隔离级别,值得庆幸的是,innodb的RR级别通过MVCC多版本并发控制,解决了在RR级别下的幻读问题,所以理论上Innodb是完全满足事务的ACID属性的,想详细了解MVCC的同学可自行搜索引擎或者看书,本文由于篇幅的原因,暂不详细展开。
总结
理解了上面说的这些概念,就很容易理解事务的隔离级别了,下面是对事务隔离级别的总结:
- 未提交读:允许脏读,A事务可以读到B事务未提交的数据
- 已提交读:A事务只能读到B事务已提交的数据
- 可重复读:A事务对于同一行数据,前后两次读到的数据一定是一样的,即使B事务对它执行了修改,且提交了,结果也不会变。
- 串行化:完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞