文章目录
1. 什么是MVCC
MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。
MVCC没有正式的标准,在不同的DBMS中 MVCC的实现方式可能是不同的,也不是普遍使用的(大家可以参考相关的DBMS文档)。这里讲解InnoDB 中 MVCC的实现机制(MySQL其它的存储引擎并不支持它)。
2. 快照读与当前读
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读,而这个读指的就是快照读,而非当前读。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。
2.1 快照读
快照读又叫一致性读,读取的是快照数据。不加锁的简单的 SELECT 都属于快照读,即不加锁的非阻塞读;比如这样:
SELECT * FROM player WHERE ...
之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。
既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。
2.2 当前读
当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:
SELECT * FROM student LOCK IN SHARE MODE; # 共享锁
SELECT * FROM student FOR UPDATE; # 排他锁
INSERT INTO student values ... # 排他锁
DELETE FROM student WHERE ... # 排他锁
UPDATE student SET ... # 排他锁
注意:InnoDB增删改默认加X锁,查默认不加锁
3. 复习
3.1 再谈隔离级别
事务有 4 个隔离级别,可能存在三种并发问题:(准确来说是四种,还有一种:脏写)

在MySQL中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,如果仅从定义的角度来看,它并不能解决幻读问题。如果想要解决幻读问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样一来就会大幅降低数据库的事务并发能力
MVCC可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题!它可以在大多数情况下替代行级锁,降低系统的开销。

在面试的时候要按照第二幅图进行回答哦~
如果采用加锁的方式,那么就是间隙锁解决幻读问题。
3.2 隐藏字段、Undo Log版本链
回顾一下undo日志的版本链,对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。
trx_id: 每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
**举例:**student表数据如下
SELECT * FROM student ;
/*
+----+--------+--------+
| id | name | class |
+----+--------+--------+
| 1 | 张三 | 一班 |
+----+--------+--------+
1 row in set (0.07 sec)
*/
假设插入该记录的事务id为8,那么此刻该条记录的示意图如下所示:

insert undo只在事务
回滚时起作用,当事务提交后,该类型的undo日志就没用了,它占用的Undo Log Segment也会被系统回收(也就是该undo日志占用的Undo页面链表要么被重用,要么被释放)。
假设之后两个事务id分别为10、20的事务对这条记录进行UPDATE 操作,操作流程如下:
| 发生时间顺序 | 事务10 | 事务20 |
|---|---|---|
| 1 | BEGIN; | |
| 2 | BEGIN; | |
| 3 | UPDATE student SET name=“李四” WHERE id=1; |
|
| 4 | UPDATE student SET name=“王五” WHERE id=1; |
|
| 5 | COMMIT; | |
| 6 | UPDATE student SET name=“钱七” WHERE id=1; |
|
| 7 | UPDATE student SET name=“宋八” WHERE id=1; |
|
| 8 | COMMIT; |
能不能在两个事务中交叉更新同一条记录呢?
不能!这不就是一个事务修改了另一个未提交事务修改过的数据,脏写。InnoDB使用锁来保证不会有脏写情况的发生,也就是在第一个事务更新了某条记录后,就会给这条记录加锁,另一个事务再次更新时就需要等待第一个事务提交了,把锁释放之后才可以继续更新。
InnoDB增删改默认加x锁,查默认不加锁
每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表:
update写的时候是默认加了X锁的,20会等待10

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,把这个链表称之为版本链

文章详细介绍了MVCC(多版本并发控制)的概念和在InnoDB存储引擎中的实现,包括快照读和当前读的区别,以及ReadView在确定数据可见性中的作用。同时,讨论了不同隔离级别(如READCOMMITTED和REPEATABLEREAD)下MVCC的工作机制,并通过实例展示了如何解决幻读问题,强调了MVCC在提高数据库并发性能和解决并发问题上的重要性。
最低0.47元/天 解锁文章
291

被折叠的 条评论
为什么被折叠?



