简介
MVCC(Multi-Version Concurrency Control)即多版本并发控制,主要是为了提高数据库的并发性能
同一行数据平时发生读写请求时,会被上锁阻塞住,但mvcc用更好的方式去处理读写请求,使其不用加锁。
这个读时快照读,而不是当前读,当前读是一种加锁操作,即悲观锁。
快照读、当前读
当前读
他读取的数据库记录,都是当前最新的版本,会对当前读取得数据进行加锁,防止其他食物修改数据。是悲观锁的一种操作。如update、insert、delete
快照读
快照读的实现是基于多版本并发控制,即MVCC,所以他读到的数据可能不是最新数据,可能是之前的数据。如不加锁的select操作
快照读和mvcc的关系
mvcc是“维持一个数据的多个版本,使读写操作没有冲突“的一个抽象概念
mvcc的实现需要快照读。
数据库的并发场景
读-读:不存在问题,不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,脏读、不可重复读、幻读
写-写:有线程安全问题,可能存在更新丢失问题
MVCC解决并发问题
用来解决读-写冲突的无锁并发控制,为事务分配单项增长和时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联。
读操作只读取该事务开始前的数据库快照
- 并发读-写时:可以做到读操作不阻塞写操作,同时写操作也不阻塞读操作
- 解决脏读、不可重复读、幻读等事务隔离性问题
- 不能解决写-写更新丢失问题
解决方案
MVCC+悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
MVCC+乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突
MVCC实现原理
由版本链、undo日志、ReadView实现
版本链
在数据库中的每行数据,除了肉眼可见的数据,还有几个隐藏字段:db_trx_id、db_roll_pointer、db_row_id
- db_trx_id:事务ID(自增):记录创建这条记录/最后一次修改该记录的事务ID
- db_roll_pointer:回滚指针:指向这条记录的上一个版本
- db_row_id:隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以db_row_id产生一个聚簇索引。
- 实际还有一个删除flag的隐藏字段,记录被更新或删除并不代表真的删除,而实删除flag变了
每次对数据库记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer,然后可以将这些日志串成一个链表。
ReadView
事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照。
记录并维护系统当前活跃事务的ID(没有commit,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被本事务看到的其他事务id列表。
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
属性
- m_ids: 当前系统活跃(未提交)事务版本号集合。
- max_trx_id: 当前read view 时“当前系统最大事务版本号+1”,即下次要分配的事务id
- min_trx_id: 当前read view 时“系统正处于活跃事务最小版本号”
- creator_trx_id: 创建当前read view的事务版本号;
trx_id==creator_trx_id:可以访问这个版本
trx_id<min_trx_id:可以访问这个版本
trx_id>max_trx_id:不可以访问这个版本
min_trx_id<=trx_id<=max_trx_id:如果trx_id在m_ids中是不可以访问这个版本的,反之可以
MVCC和事务隔离级别
上面所讲的Read View用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。
RC、RR生成时机
- RC隔离级别下,是每个快照读(select)都会生成并获取最新的ReadView;即一个事务中的每个select都会获得一个新的视图(ReadView),所以会造成不可重复读
- 而在RR隔离级别下,则是同一个事务中的第一个快照读(即select)才会创建ReadView, 之后的快照读获取的都是同一个视图ReadView,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的,这样就解决了不可重复读的问题
解决幻读问题
快照读:通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。
当前读:通过next-key锁(行锁+间隙锁)来解决问题的。
RC、RR级别下的InnoDB快照读区别
在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因
在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个ReadView,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个ReadView,所以对之后的修改不可见;
即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见