数据库事务隔离级别
事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)。
- 读未提交:一个事务还未提交时,它做的变更就被别的事务看到
- 读提交:一个事务提交之后,它做的变更才会被其它事务看到
- 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的
- 串行化:对于一行记录,读和写均会加锁。出现读写锁冲突时,后面的事务需要等待前一个事务执行完成。
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在可重复读隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在读提交隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。这里需要注意的是读未提交隔离级别下直接返回记录上的最新值,没有视图概念;而串行化隔离级别下直接用加锁的方式来避免并行访问。
数据库事务隔离级别和MVCC(多版本并发控制)的关系
以下内容均是基于Mysql中Innodb引擎
视图的概念
- 视图(View),是数据库中的虚拟表。数据库中只存放视图的定义,数据仍然保存在原来的基本表中。
- 一致性读视图(Consistent read view),Innodb引擎实现MVCC时使用,用于支持读提交和可重复读隔离级别的实现。
MVCC
Multi-Version Concurrency Control 多版本并发,MVCC 是一种并发控制的方法,用于实现对数据库的并发访问。InnoDB MVCC的实现基于Undo log,通过回滚段来构建需要的版本记录,通过Read view来判断哪些版本的数据可见。
版本链
对于InnoDB引擎的表来说,它的聚簇索引记录中的数据会包含两个必要的隐藏列trx_id和roll_pointer
trx_id: 每次一个事务对某条记录进行改动时,会把该事务的事务id赋值给trx_id
roll_pointer: 每次对记录进行改动时,会把旧的版本写入undo log日志中,每条undo log也有一个roll_pointer指针,通过这个指针可以找到该记录修改前的记录
通过roll_pointer指针可以将记录的多个版本连接成一个链表,这个链表就是记录的版本记录,每次需要历史版本的数据可以通过当前版本和undo log计算出来
Read view
虽然通过构建版本链,可以找到记录的历史版本,但是历史版本那么多,哪个版本才是要读的数据呢?这个时候就需要视图大显身手,通过视图来进行版本可见性判断。
对于读未提交的事务来说,可以读到未提交的事务修改的记录,所以直接读取记录的最新版本就行;对于串行化隔离级别来说,需要使用加锁的方式来访问记录。对于读提交和可重复读的事务来说,需要使用Read view来进行判断哪个版本是当前事务可见的。
InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务ID。“活跃”指的就是,启动了但还没提交。
Read view主要结构:
- m_ids:表示创建ReadView时活跃的读写事务列表
- m_low_limit_id:表示⽣成ReadView时系统中应该分配给下⼀ 个事务的id值,事务ID大于等于该值的数据修改不可见
- m_up_limit_id:m_ids中的最小值,事务ID小于该值的数据修改可见
可见性判断:
- 如果记录trx_id小于m_up_limit_id或者等于m_creator_trx_id,表明ReadView创建的时候该事务已经提交,记录可见
- 如果记录的trx_id大于等于m_low_limit_id,表明事务是在ReadView创建后开启的,其修改,插入的记录不可见
- 当trx_id在m_up_limit_id和m_low_limit_id之间的时候,如果id在m_ids数组中,表明ReadView创建时候,事务处于活跃状态,因此记录不可见。
总结
MVCC指的就是在使⽤READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执⾏普通的SEELCT操作时访问记录的版本链的过程,这样⼦可以使不同事 的读-写、写-读操作并发执⾏,从⽽提升系统性能。
RC和RR这两个隔离级别不同的地方在于:
- 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
- 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。
参考
极客时间mysql45讲