MySQL的事务原理及MVCC

事务

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

事务特性

原子性:指事务不可分割,要么全部成功,要么全部失败,不可能存在部分成功或部分失败的情况。如果执行某一条语句失败后,将会触发之前所有执行过的语句的回滚,因此靠的是undo log。

一致性:在事务执行前后,数据的完整性没有遭到破坏。一致性是mysql追求的最终目标,需要数据库层面与应用层面同时来维护。需要先满足原子性、隔离性与持久性,同时也需要应用层面做保障,即在应用层面对数据进行检验。

隔离性:事务之前是隔离的,并发执行的事务之间不存在互相影响,mysql通过锁以及MVCC来保证隔离性。

持久性:事务一旦提交,那么对数据的操作就是永久性的,即使接下来数据库宕机也不会有影响。mysql是通过redo log来实现宕机恢复的,而binlog主要是用来误删恢复与主从复制的。

脏读、不可重复读、幻读

脏读:

读取到了别的事务未提交的数据。即事务a读取到了事务b还未提交的数据,此时,若事务b发生错误,需要进行回滚,那么事务a读到的数据就是回滚后的数据,也就是脏数据。

不可重复读:

一个事务执行期间对于同一条记录,前后读到的数据不一样。即事务a第一次读取一行数据,读完后,事务b对这条数据进行了修改操作,而因为业务需求,此时事务a还需要第二次读取该条数据,第二次读取到的是已经修改过的数据,也就是说事务a第一次和第二次对同一条数据的读取是不重复的。

幻读:

一个事务前后多次读取,读到的记录数不同。假设事务a第一次读到了3条数据,此时事务b执行了新增操作,记录数增多,事务a再次读取,发现读取到的记录数与第一次不同,叫做发生幻读。

事务隔离级别

事务隔离级别就是对上述问题进行不同程度的处理。

事务隔离级别有四种:

读未提交(Read Uncommitted)

读已提交(Read Committed)

可重复读(Repeatable Read)

串行化(Serializable)

读未提交:

允许事务a读取事务b还未提交的数据,没有对上述问题有所解决

读已提交:

允许事务a读取事务b已提交的数据,解决了脏读的问题,但会出现一个事务前后读取到的数据不同,即不可重复读问题。

可重复读:

可重复读是mysql默认的事务隔离级别,即一个事务前后读取到的数据是重复的,解决了脏读以及不可重复读的问题,但是还会存在一个事务前后读取到的记录数不同,即幻读问题。

串行化:

这种隔离级别下,事务顺序执行,所以它们之间不会发生冲突,解决了脏读、不可重复读以及幻读的问题,但是,串行化的效率太低了,所以一般使用可重复读的隔离级别。

事务隔离级别 脏读 不可重复读 幻读
读未提交 N N N
读已提交 Y N N
可重复读 Y Y N
串行化 Y Y Y

MVCC

mysql为了实现以上隔离级别,提出了LBCC(Lock-Based Concurrent Control,基于锁的并发控制)与MVCC(Multi-Version Concurrent Control,基于多版本的并发控制)。

在LBCC中,读写冲突,会使用诸如记录锁、间隙锁与临键锁等锁来实现数据的并发安全,因此读写性能不高。

在MVCC中,读写不冲突,记录每一行的多个版本,来避免在多个事务之间的竞争。以空间换时间的思路,极大地提高了读写性能。

MVCC的实现依赖三个方面

隐式字段:创建一张表时,mysql会自动添加上一些隐式字段,如DB_TRX_ID、DB_ROLL_PTR,如果表中没有设置主键,还会添加一个DB_ROW_ID的字段。

undo log版本链:记录一条数据的多个版本,形成一条链表,头结点是最新的数据,尾部是最早的数据。

readview(读视图):是快照读SQL执行时MVCC提供数据的依据,记录并维护系统当前活跃的事务(未提交)ID。

readview读取版本数据链的规则:

MVCC在四种隔离级别下的区别


在Read Uncommitted级别下,事务总是读取到最新的数据,因此根本用不到历史版本,所以MVCC不在该级别下工作。

在Serializable级别下,事务总是顺序执行。写会加写锁,读会加读锁,完全用不到MVCC,所以MVCC也不在该级别下工作。

真正和MVCC兼容的隔离级别是Read Committed(RC)与Repeatable Read(RR)

MVCC在RC与RR级别下的区别,在于生成ReadView的频率不同。

在RC级别下,当前事务总是希望读取到别的事务已经提交的数据,因此当前事务事务会在执行每一次快照读的情况下都会去生成ReadView,实时更新m_ids,及时发现那些已经提交的事务。

在RR级别下,当前事务当然也能够读取到别的事务已经提交的数据,但为了避免不可重复读,因此只会在执行第一次快照读的情况下去生成ReadView,之后的快照读会一直沿用该ReadView。