MVCC多版本并发控制
MVCC多版本并发控制一种并发控制的方法,一般用在数据库管理系统,实现对数据库的并发访问,在编程语言中实现事务内存。
- 对于mysql innodb的应用主要是为了提高并发性能,用更好的方式处理读写冲突,做到即便有读写冲突,也可以做到不加锁的非阻塞并发读。
mvcc解决的问题
- 对于数据库而言,存在三种并发场景:
- 读读:不存在问题。
- 读写:有线程安全问题,可能造成事务隔离性问题。
- 写写:有线程安全问题,可能存在更新丢失问题。
因为mvcc是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,所以可以解决:
- 并发读写时,可以做到读写不阻塞,提供并发读写性能。
- 解决幻读、脏读、不可重复读等事务隔离问题,但是不能解决更新丢失问题。
引入
- 因为对于不同版本的事务隔离级别,读取到的数据是一不一样的,主要在于当前执行sql的时候是当前读还是快照读。
当前读
读取最新的数据,比如加锁的select。
读取的时候保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
select * from xx_table lock in share mode
select * from xx_table for update;
insert into xx_table;
快照读
读取的是历史版本的数据,即快照生成的那一刻的数据。
- 快照读产生的前提:基于提供并发性能的考虑,快照读的实现基于MVCC。
- 快照读的前提:隔离级别不是串行化,串行化下:快照读会退化为当前读。
- 像不加锁的select就是快照读。
select * from table where ...
RC、RR级别下Innodb快照读有什么不同
- RC下,一个事务中的每次select都会产生一个read view。
- RR下,一个事务中,只在第一次select中产生一次read view。
快照读、MVCC关系
- mvcc指的是一个数据库的多个版本,使的读写操作没有冲突。
- 快照读是mysql为实现MVCC的一个非阻塞读功能。
MVCC实现原理
mvcc在mysql 的实现由三个组件实现:
- 三个隐式字段。
- undolog。
- read view。
隐藏字段
- 除了每行记录我们自定义的字段外,还有数据库隐式定义的三个字段。
- DB_TRX_ID:6字节,当前事务的ID,绑定在每一个行上面,每次都是递增的。
- DB_ROLL_PTR:7字节,回滚指针,指向这条记录的上一个版本,配合undolog,指向上一个旧版本。
- DB_ROW_ID:6字节,隐藏主键,因为innodb是一个聚簇索引,当进行数据插入的时候,必须跟某个索引列绑定在一起,如果有主键则用主键,没有的话,看下是否有唯一键,如果有则用,否则使用6字节的rowID。
比如上图
- DB_ROW_ID:是数据库默认为改行记录生成的唯一隐式主键。
- DB_TRX_ID:当前操作该记录的事务ID。
- DB_ROLL_PTR:回滚指针。
注意:刚插入数据的时候,DB_ROLL_PTR是空的。
undo log
称为回滚日志,表示在insert/delete/update操作时删除的方便回滚的日志。
- 在insert的时候,产生的日志只在事务回滚的时候用,并且在事务提交后可以被立即丢弃,多个版本的历史数据是不会持久保留的,当持久化到磁盘之后,会删除对应的数据。
- 在update/delete的时候,产生的日志不仅在事务回滚,而且在快照读的时候也需要,所以不能随意删除,只有在快照读/事务回滚不涉及该日志的时候,对应的日志才会被purge线程统一清楚。(因为对于更新/删除,只是设置一下老记录的deleted_bit,并不是真正将过时记录删除,为了节省磁盘空间,innodb有专门的purge现场来清除deleted_bit=true的记录,如果某个记录的delete_bit=true,而且DB_TRX_ID和purge线程的read view可见,那么这条记录一定是可以被清除的)。
undolog记录链生成流程
- 先加排它锁
- 拷贝数据到undolog
- 修改行数据和隐藏事务id
- 回滚指针指向undolog副本记录
- 提交事务释放锁
read view
是事务进行快照读操作时蔡山的读事图,在该事物执行快照的那一刻,会生成一个数据库系统当前的快照,记录并维护当前系统活跃的事务id。
作用
可见性判断,即之前做的修改能否对现在的读操作产生影响。也就是说当某个事务在执行快照读的时候,对该记录创建一个read view,把它当做条件,判断当前事务能否看到那个版本,因为有可能读到的是最新的数据,也有可能是历史版本的数据。
遵循原则
遵循可见算法,将被修改数据的最新记录中的DB_TRX_ID与系统其他活跃的事务ID去对比。
可见性规则
read view有三个全局属性:
- trx_list:一个数值列表,用来维护read view生成时刻系统正在活跃的事务ID。
- up_limit_id:记录trx_list列表中最小的事务ID。
- low_limit_id:read view生成时刻系统尚未分配的下一个事务ID(下一个事务的最新ID)
具体规则:
- 判断TRX_ID<UP_LIMIT_ID是否成立,如果成立,表名生成该版本的事务在当前事务生成read view前以及提交,则当前事务可以看到TRX_ID所在的记录,如果不成立进入下一个判断。
- TRX_ID>=LOW_LIMIT_ID是否成立,成立:表明该版本的事务在当前事务生成readview之后生成,即看不到当前事务;不成立进入下一个判断。
- 判断当前事务ID是否存在trx_list中,如果在,表名创建read view生成该版本的时候事务还在活跃,故当前版本不能被访问;如果不在,说明,创建read view的时候,该版本的事务已经提交,说明该版本是可以被访问的。