MVCC 即 多版本并发控制,它主要是通过维护了一个数据的多个版本,用一种无锁的方式,来解决并发过程中的读写冲突;想要掌握MVCC,需要了解几个概念:数据库的锁,快照读,当前读。
概念补充
数据库的锁
根据锁的属性划分为:共享锁(S锁),排它锁(X锁)
根据锁的粒度划分为:表锁,行锁,页锁,记录锁,间隙锁,临键锁;
共享锁:共享锁又称为读锁,简称:S锁;当一个事务为数据加上读锁以后,其他事务只能为数据加读锁,而不能对数据加写锁;直到所有的读锁释放以后,才能够加写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候,不能够对数据进行修改。
排它锁:排它锁又称为写锁,简称:X锁;当一个事务未数据加上写锁以后,其他事务不能够为数据加任何锁,直到该锁释放以后,其他事务才能为数据加锁。排它锁的目的是在数据修改的时候,不允许其他人修改数据,也不允许其他人读取数据,避免出现脏数据和脏读的问题。
表锁:表锁指上锁的时候锁住的是整个表,其他事务访问该表时,需要等到表锁释放以后才能够继续访问;特点是:加锁简单,锁的粒度大,容易产生冲突。
行锁:行锁指上锁的时候锁住的是一条或者多条记录,其他事务访问该表的记录时,只有被锁住的记录不能访问,其他的数据可以正常访问;特点是:粒度小,加锁复杂,并发度高,不容易冲突。
记录锁:也属于行锁的一种,只不过锁定的数据范围只是表中的某一条记录。
页锁:属于行锁和表锁之间的一种锁。
间隙锁:也是行锁的一种,锁定的是一个数据范围;当表相邻ID之间出现空隙,则会形成一个区间,遵循左开右闭的原则;间隙锁只会出现在REPEATABLE READ (重复读)隔离级别中。
临键锁:也是行锁的一种,并且它是InnoDB行锁默认的算法,总结的说它就是记录锁和间隙锁的组合,临键锁会把查询出来的数据锁住,同时也会把查询范围的所有间隙锁住,也会把相邻的下一个区间也锁住。
当前读
为什么叫当前读?因为它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录加锁。
select lock in share mode (共享锁),select fro update,insert,update,delete (排它锁);这些操作都是一种当前读。
快照读
快照读是为了提高数据库的并发读取能力,不加锁的select操作就是快照读,即不加锁的非阻塞读。快照读是基于多版本并发控制,既然是记录有多个版本,那么快照读所读取的记录不一定是数据的最新版本,可能是历史版本。
MVCC
MVCC 的实现是基于记录的三个隐藏字段,undo log,readview 实现的。
隐藏字段
每行记录除了自己定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段。
DB_TRX_ID
6字节,最近修改的事务id,记录创建或者修改这条事务的id。
DB_ROLL_PTR
7字节,回滚指针,指向这条记录的上一个版本,用于跟redo log配合
DB_ROW_ID
6字节,隐藏的主键,如果数据表没有主键,InnoDB会自动生成一个6自建的ow_id
undo log
undo log 用于事务回滚时,撤销事务的操作。
当进行insert操作时,产生的undo log 只在事务回滚时需要,事务提交后可以立刻被丢弃。
当进行update 或者 delete 操作时,产生的undo log不仅在事务回滚时需要,在快照读时也需要,所以不能随便删除。
undo log 记录链
事务编号1,插入一条数据时:
事务编号2,将name 从zhangsan 修改成lisi,修改age为32;
通过上图可知,多个事务在对同一行记录进行修改时,会产生一个undo log记录链,链表的首部是该记录”最新的旧记录“,链表的尾部是”最旧的旧记录“。
Read view
Read view 是事务进行快照读操作时产生的读视图;当事务进行快照读的那一刻,会生成一个数据系统当前的快照,并记录当前活跃事务的id,事务id是递增的。
其实Read view 最大的作用是做可见性判断;也就是说当某个事务进行快照读时,对该记录创建一个Read view的视图,把它当做条件去判断当前事务能看到数据的那个版本,有可能读到的是最新的数据,也有可能读取到是的undo log中的记录的某个版本。
Read view 全局属性如下:
trx_list: 生产Read View时刻正在活跃的事务Id。
up_limit_id: 记录trx_list列表中,事务Id最新的Id。
low_limit_id:Read view 生成时刻,系统尚未分配的下一个事务Id。
Read view 的可见性算法判断,主要是将要被修改的数据的最新记录中的DB_TRX_ID取出来,与系统中其他活跃事务的id作比较,如果DB_TRX_ID跟Read view属性做了比较,不符合可见性,则通过DB_ROLL_PTR指针逐步去遍历undo log 中的记录,知道找到符合可见性的,就是当前事务能看的最新老版本数据。
比较规则如下:
1、首先比较DB_TRX_ID < up_limit_id ? 如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于,则进行下一步判断。
2、接下来判断DB_TRX_ID >= low_limit_id?如果大于,则代表DB_TRX_ID所在的记录在Read view 生成之后才出现的,那么对于当前事务肯定不可见;如果下于,则进行下一步判断。
3、判断DB_TRX_ID 是否在活跃事务列表;如果在,则代表生成Read view 的时候,这个事务还未commit,所以修改的事务,当前事务也看不到;如果不在,则说明这个事务在生成Read view 之前就已经开始commit,那么修改的结果能够可见。
RC 和 RR 下,快照读有什么区别?
在RR级别下,某个事务对某个记录的第一次快照读会生成一个Read view,将当前系统活跃的其他事务记录起来,此后调用快照读的时候,还是使用同一个Read view,所以只要当前事务在其他事务提交更新事前使用过快照读,那么之后的快照读使用的都是同一个Read view。
在RC级别下,事务中的快照读,每次会生成一个新的Read view,这就是我们在RC级别下的事务中,可以看到别的事务更新的原因。
- 本文链接: https://www.sunce.wang/archives/mysql-xi-lie-san-mvcc-yuan-li-
- 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!