什么是MVCC(多版本并发控制)?
MVCC:通过数据行的多个版本管理来实现数据库的并发控制。所谓的MVCC就是生成一个ReadView(后面有详解),通过ReadView找到符合条件的记录版本,查询语句只能读到ReadView之前已提交事务的更改内容,ReadView之前未提交的事务或是在ReadView之后的事务都无法被读到。
ReadView
引出
在MVCC机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照都保存在Undo Log里,如果来了一个事务想要查询这个行记录,此时要读取哪个版本的行记录就用到了ReadView,它帮我门解决了行的可见性问题。
原理
ReadView 就是事务在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,系统会生成数据库系统当前的一个快照,InnoDB 为每个事务构造了一个数组用来记录并维护当前活跃事务的ID(启动了但还未提交的事务)。
MVCC整体操作流程
当我们查询一条数据时,系统如何通过MVCC找到它:
- 获取事务自己的版本号(事务ID);
- 获取ReadView;
- 查询得到的数据,然后与ReadView中的事务版本号进行比较;
- 如果不符合ReadView,就需要从Undo Log 中获取历史快照;
- 最后返回符合规则的数据。
若某个版本的数据对当前事务不可见,就会顺着版本链找到下一个版本的数据,继续按照上面的步骤判断可见性,若事务一直是不可见,会继续找,直到版本链的最后一个版本,若是最后一个版本也不可见就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
ReadView 判断可见性的规则
在访问某数据时,会经过以下步骤判断某个版本是否可见:
访问版本的trx_id (即事务id):
- 若是与creator_trx_id(当前ReadView的事务ID)相同,说明当前事务在访问自己修改过的事务,固该版本可被当前事务访问;
- 若trx_id 处于 ReadView 的up_limit_id 和 low_limit_id 之间(处于可访问事务id的范围内),就需要判断trx_id是否存在于trx_ids列表中(存放了可被访问事务的id);若是存在,则说明该事务在创建ReadView时还是活跃的,该版本不可被访问,若是不存在,说明在创建ReadView时,该事务已被提交,该版本可被访问。
MVCC 解决脏读、不可重复读、幻读
普通的SELECT语句在READ COMMITTED 和 REPEATABLE READ隔离级别下会使用MVCC读取记录。
- READ COMMITTED隔离级别:一个事务在执行过程中每次执行SELECT都会生成一个ReadView:保证了事务不可以读取到未提交到事务所做的更改,避免了脏读;
- REPEATABLE READ隔离级别:事务在执行过程中只有第一次执行SELECT操作才会生成一个ReadView,之后的SELECT操作都复用这个ReadView,避免了不可重复读和幻读。
快照读与当前读
快照读:MVCC在MySQL InnoDB中的实现为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到有读写冲突时也能做到不加锁:非阻塞并发读,这个读就是快照读;
当前读:一种加锁的操作(悲观锁的实现),而MVCC 采用的是乐观锁的思想。
- 当前读读取到的数据是最新版本(非历史数据),读取时还需保证其他并发事务不能修改当前记录,会对读取到的数据进行加锁。