-
引言
-
什么是MVCC
-
MVCC解决了什么问题
-
什么是当前读和快照读
-
当前读
-
快照读
-
-
数据库隔离级别
-
MVCC适用的隔离级别
-
MVCC的实现原理
-
undo log和redo log在MVCC中的作用
-
redo log
-
undo log
-
-
面试中的MVCC
-
总结
引言
话说村里有户人家小孩感冒了,去医院看病,医药费还没付。但是孩子的父亲和外公没有互相商量好,于是就同时登录了医院智慧收费系统。
-
使用悲观锁
假设孩子父亲先查询收费清单,当要进行支付的时候,这个清单在数据库中的行数据会被锁住。那么此时,孩子外公是无法访问该数据的,直到孩子父亲完成支付或者支付失败,将悲观锁释放了才可以继续访问。悲观锁采用的是 「先获取锁再访问」 的策略,来保障数据的安全。
可是这样岂不是很没有用户体验,如果多个亲属要查看同一份报告,岂不是只能都凑到一台设备上观看。
-
使用乐观锁
在孩子父亲进行支付的同时,孩子外公也可以访问到数据。乐观锁采用的是 「提交更新才进行检测冲突」 的策略,来保障数据的安全。
什么是MVCC
MVCC(Multi Version Concurrency Control的简称),代表多版本并发控制。
MVCC最大的优势:读不加锁,读写不冲突。读写不冲突是非常重要的,极大的增加了系统的并发性能。MVCC机制也是乐观锁的一种体现。
如果想要有更深的学术研究可以阅读这个文章《An empirical evaluation of in-memory multi-version concurrency control》,对主流的MVCC技术要点进行了完整的梳理,并且在自研的Peloton数据库实现了所有Approaches,用于固定变量进行横向测试对比。
摘要主要为:
Multi-version concurrency control (MVCC) is currently the most popular transaction management scheme in modern database management systems (DBMSs). Although MVCC was discovered in the late 1970s, it is used in almost every major relational DBMS released in the last decade.
翻译一下就是:
多版本并发控制(MVCC)是当前数据库管理系统中最流行的事务管理方案。尽管MVCC是在20世纪70年代末被发现的,但在过去十年中,几乎所有主要的关系型DBMS都使用了MVCC。
如果没有什么耐心,没关系,我简单剖析一下MVCC一些面试常考的知识点。这篇论文偏学术,如果对MVCC有更深研究的可以download一下
https://dl.acm.org/doi/abs/10.14778/3067421.3067427
InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的,分别保存这条行的创建时间和行的删除时间。这里的时间并不是实际的时间值,而是系统版本号(事务的ID)。事务开始时刻的系统版本号会作为事务的ID,每次执行一个新事务,系统版本号就会自动递增。随之引出了当前读和快照读。不过,这可没有你想象的那么简单,例如这张图
MVCC解决了什么问题
多事务的并发进行一般会造成以下几个问题:
脏读: A事务读取到了B事务未提交的内容,而B事务后面进行了回滚.
不可重复读: 当设置A事务只能读取B事务已经提交的部分,会造成在A事务内的两次查询,结果竟然不一样,因为在此期间B事务进行了提交操作.
幻读: A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据.造成"幻觉".
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
什么是当前读和快照读
当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读。
当前读就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
快照读
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;
之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
不加锁的简单的 SELECT 都属于快照读,例如:
SELECT * FROM t WHERE id=1
与 快照读 相对应的则是 当前读,当前读就是读取最新数据,而不是历史版本的数据。加锁的 SELECT 就属于当前读,例如:
SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE; SELECT * FROM t WHERE id=1 FOR UPDATE;
数据库隔离级别
要说MVCC,就不能脱离数据库的隔离级别。
大家都知道数据库事务具备ACID特性,即Atomicity(原子性) Consistency(一致性), Isolation(隔离性), Durability(持久性)
原子性:要执行的事务是一个独立的操作单元,要么全部执行,要么全部不执行
一致性:事务的一致性是指事务的执行不能破坏数据库的一致性,一致性也称为完整性。一个事务在执行后,数据库必须从一个一致性状态转变为另一个一致性状态。
隔离性:多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
持久性(durability):事务一旦提交,则其所有的修改将会保存到数据库。即使此时系统崩溃,修改的数据也不会丢失。
读未提交(READ UNCOMMITED)->读已提交(READ COMMITTED)->可重复读(REPEATABLE READ)->序列化(SERIALIZABLE)。隔离级别依次增强,但是导致的问题是并发能力的减弱。
MVCC适用的隔离级别
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。
REPEATABLE READ读取之前系统版本号的记录,保证同一个事务中多次读取结果一致。
REPEATABLE READ隔离级别下,MVCC具体操作:
SELECT操作,InnoDB会根据以下两个条件检查每行记录:
a. InnoDB只查找创建版本号早于或等于当前系统版本号的数据行,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
b. 行的删除版本号要么未定义,要么大于当前的系统版本号(在当前事务开始之后删除的)。这可以确保事务读取到的行,在事务开始之前未被删除。
READ COMMITTED读取最新的版本号记录,就是所有事务最新提交的结果。
其他两个隔离级别和MVCC不兼容。READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。SERIALIZABLE会对所有读取的行都加锁。
MVCC的实现原理
每一行记录有三个隐藏键,分别为DATA_TRX_ID、DATA_ROLL_PTR、DB_ROW_ID,其中:
DATA_TRX_ID
记录最近更新这条行记录的事务 ID,大小为 6 个字节
DATA_ROLL_PTR
表示指向该行回滚段(rollback segment)的指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。
DB_ROW_ID
行标识(隐藏单调自增 ID),大小为 6 字节,如果表没有主键,InnoDB 会自动生成一个隐藏主键,即此列。
为了让大家更直观地理解 MVCC 的实现原理,这里举一个“事务对某行记录更新的过程”的案例来讲解 MVCC 中多版本的实现。
假设 F1~F6 是表中字段的名字,1~6 是其对应的数据。后面三个隐含字段分别对应该行的隐含ID、事务号和回滚指针,如下图所示。
具体的更新过程,简单描述如下。
首先,假如这条数据是刚 INSERT 的,可以认为 ID 为 1,其他两个字段为空。
然后,当事务 1 更改该行的数据值时,会进行如下操作,如下图所示。
把该行修改前的值复制到 Undo log,即图中下面的行;
修改当前行的值,填写事务编号,使回滚指针指向 Undo log 中修改前的行。
接下来,与事务 1 相同,此时 Undo log 中有两行记录,并且通过回滚指针连在一起。因此,如果 Undo log 一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的是在 InnoDB 中存在 purge 线程,它会查询那些比现在最老的活动事务还早的 Undo log,并删除它们,从而保证 Undo log 文件不会无限增长,如下图所示。
如果此时事务回滚,那么可以根据回滚指针,对数据依次进行回滚并找到undo log中数据行为当前事务id的一列,进行数据恢复。
undo log和redo log在MVCC中的作用
redo log 和undo log相对应,它是将事务操作的最新数据存储起来。
redo log
redo log主要是为了实现事务的持久性而产生的。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的未入磁盘数据进行持久化这一特性。
undo log
MySQL InnoDB是使用undo log实现多版本并发控制(MVCC)。事务未提交之前,undo log保存未提交数据之前的数据版本,当读取某一行被其他事务操作时,可以从undo log中分析出该行之前记录的数据。 undo log中的数据作为数据旧版本快照,提供事务之间的并发处理。
在多个事务同时进行时,为了保证某个事务可以正确地读取到隔离级别要求的数据版本,InnoDB会将当前系统中活跃的事务列表创建一个副本,副本中保存的是系统中当前不应该被本事务看到的其他事务列表。当本事务要读某一行记录时,InnoDB通过副本 (ReadView) 决定是否读取该行记录的某个版本,并且从undo log中寻找相应的版本。
在执行事务的时候,我们知道当调用roll back命令的时候,数据就会还原,这里用到的原理就是undo,也是实现事务原子性的原理。
面试中的MVCC
牛客篇幅有限,剩余的内容明天发,着急的小伙伴可以关注公众号JerryCodes,早享早快乐!
总结
1、MVCC的实现,是通过保存数据在某个时间点的快照来实现的。
2、因此一个事务只需在启动时声明:以我启动时刻为准;
如果一个数据版本是在我启动前生成的,就认;
启动后才生成的,我不认,必须要找到它的上一个版本;
若“上个版本”也不可见,那就继续往前找;
全部评论
(0) 回帖