浅入浅出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元,这个状态分为两步即:

  1. 从A的账户扣1000元
  2. 再往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事务对它执行了修改,且提交了,结果也不会变。
  • 串行化:完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

参考

  • 《MySQL技术内幕:InnoDB存储引擎》
  • Innodb中的事务隔离级别和锁的关系

你可能感兴趣的:(浅入浅出MySQL事务)