赞
踩
MVCC(Multi-Version Concurrency Control)是一种并发控制机制,用于解决数据库并发访问中,数据一致性问题。它通过在读写操作期间保存同一个数据的多个数据版本,以提供并发事务间的隔离性,从而避免了传统的锁机制所带来的资源争用和阻塞问题。本质上就是一个空间换时间的逻辑。
MVCC是关于事务隔离级别的无锁的实现方式。它用于提高事务的并发性能。
因此,首先来简单介绍下什么是事务的隔离级别,已经了解这一内容的小伙伴可以直接跳过这部分。
并发事务会出现如下三个问题:
为了解决并发事务存在的脏读、不可重复读、幻读等问题,数据库大叔设计了四种隔离级别,下面我们用一个例子来分别看看这四个隔离级别。
建表语句:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL,
`age` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
创建一条记录:
mysql> SELECT * FROM user;
+----+-----------------+------+
| id | name | age |
+----+-----------------+------+
| 1 | 古时的风筝 | 1 |
+----+-----------------+------+
读未提交隔离级别,限制了两个数据不能同时修改,但任何事务对数据的修改都会第一时间暴露给其他事务,即使事务还没有提交。
下面来做个简单实验验证一下,首先设置全局隔离级别为读未提交。
set global transaction isolation level read uncommitted;
设置完成后,只对之后新起的 session 才起作用,对已经启动 session 无效。如果用 shell 客户端那就要重新连接 MySQL,如果用 Navicat 那就要创建新的查询窗口。
启动两个事务,分别为事务A和事务B,在事务A中使用 update 语句,修改 age 的值为10,初始是1 ,在执行完 update 语句之后,在事务B中查询 user 表,会看到 age 的值已经是 10 了,这时候事务A还没有提交,而此时事务B有可能拿着已经修改过的 age=10 去进行其他操作了。在事务B进行操作的过程中,很有可能事务A由于某些原因,进行了事务回滚操作,那其实事务B得到的就是脏数据了,拿着脏数据去进行其他的计算,那结果肯定也是有问题的。
顺着时间轴往表示两事务中操作的执行顺序,重点看图中 age 字段的值。
读未提交,其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,读未提交没办法解决脏数据问题。更别提不可重复读和幻读了,想都不要想。
结论:这级别的事务隔离有脏读、重复读、幻读的问题,基本不会使用。
既然读未提交没办法解决脏数据问题,那么就有了读提交。读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。那脏数据问题迎刃而解了。
读提交事务隔离级别是大多数流行数据库的默认事务隔离界别,比如 Oracle,但是不是 MySQL 的默认隔离界别。
我们继续来做一下验证,首先把事务隔离级别改为读提交级别。
set global transaction isolation level read committed;
之后需要重新打开新的 session 窗口,也就是新的 shell 窗口才可以。
同样开启事务A和事务B两个事务,在事务A中使用 update 语句将 id=1 的记录行 age 字段改为 10。此时,在事务B中使用 select 语句进行查询,我们发现在事务A提交之前,事务B中查询到的记录 age 一直是1,直到事务A提交,此时在事务B中 select 查询,发现 age 的值已经是 10 了。
这就出现了一个问题,在同一事务中(本例中的事务B),事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,事务A的提交影响了事务B的查询结果,这就是不可重复读,也就是读提交隔离级别。
每个 select 语句都有自己的一份快照,而不是一个事务一份,所以在不同的时刻,查询出来的数据可能是不一致的。
读已提交的隔离级别解决了脏读的问题,但还是解决不了不可重复读、幻读问题。
可重复是对比不可重复而言的,上面说不可重复读是指同一事物不同时刻读到的数据值可能不一致。而可重复读是指,事务不会读到其他事务对已有数据的修改,及时其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。
同样的,需改全局隔离级别为可重复读级别。
set global transaction isolation level repeatable read;
在这个隔离级别下,启动两个事务,两个事务同时开启。
首先看一下可重复读的效果,事务A启动后修改了数据,并且在事务B之前提交,事务B在事务开始和事务A提交之后两个时间节点都读取的数据相同,已经可以看出可重复读的效果。
可重复读做到了,这只是针对已有行的更改操作有效,但是对于新插入的行记录,就没这么幸运了,幻读就这么产生了。我们看一下这个过程:
事务A开始后,执行 update 操作,将 age = 1 的记录的 name 改为“风筝2号”;
事务B开始后,在事务执行完 update 后,执行 insert 操作,插入记录 age =1,name = 古时的风筝,这和事务A修改的那条记录值相同,然后提交。
事务B提交后,事务A中执行 select,查询 age=1 的数据,这时,会发现多了一行,并且发现还有一条 name = 古时的风筝,age = 1 的记录,这其实就是事务B刚刚插入的,这就是幻读。
需要注意的是,当你在 MySQL 中实际测试幻读的时候,并不会出现上图的结果,因为幻读并不会发生,MySQL 的可重复读隔离级别其实解决了幻读问题,它就是间隙锁,感兴趣的小伙伴可以去了解一下,这里就不介绍了。
可重复读隔离级别,解决了赃读、不可重复读和幻读的问题,并且没有用到MySql默认隔离级别
事务最高的隔离级别,在该级别下,所有事务都是进行串行化顺序执行的。
串行化隔离级别,可以避免脏读、不可重复读与幻读所有并发问题。但是这种事务隔离级别下,事务执行很耗性能。
小结: 读已提交(RC) 和 可重复读(RR) 的隔离问题就是通过MVCC来实现的。
MVCC 实现的三个关键点:
对于InnoDB存储引擎,每一行记录除了我们的原数据列之外,还有两个隐藏列trx_id、roll_pointer,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列row_id。
列名 | 是否必须 | 描述 |
---|---|---|
row_id | 否 | 单调递增的行ID,不是必需的,占用6个字节 |
trx_id | 是 | 记录操作该数据事务的事务ID |
roll_pointer | 是 | 这个隐藏字段相当于一个指针,指向回滚的undo日志 |
Undo Log 版本链是 InnoDB 存储引擎中一种重要的数据结构,用于记录数据项的历史修改信息。Undo Log 版本链由多个 Undo Log 记录组成。Undo Log 版本链通过回滚指针将多个 Undo Log 记录连接在一起,形成一个链表结构。每个数据项都维护着自己的 Undo Log 版本链,用于记录该数据项的历史修改信息。主要作用是实现事务的回滚和数据恢复。
Read View(读视图)用于管理事务之间数据可见性的一种机制。Read View 在特定时刻为事务创建的一个快照,该快照包含了在该时刻所有未提交事务的事务标识符,以及其他一些辅助信息。(这也就是后面我要和大家说的快照读)
在 Read View 中包含了以下 4 个主要的字段:
解释:
活跃的事务id:指的是已经创建,但未commit的事务,即处理中的事务id。
RC 级别中,每次快照读都会生成一个全新的 Read View,而 RR 级别中同一个事务会复用一个 Read View。
有了 Read View 和 Undo Log 版本链之后,并发事务在查询数据时就知道要读取哪些数据了。
判断方法是根据 Read View 中的 4 个重要字段,先去 Undo Log 版本链中最新的数据行进行比对,如果满足下面 Read View 的判断条件,则返回当前行的数据,如果不满足则继续查找 Undo Log版本链的下一行数据,直到找到满足的条件的数据为止,如果查询完没有满足条件的数据,则返回 NULL。
对于删除的情况,会将版本链的最新数据复制一份,然后将trx_id修改成删除操作的trx_id,同时会在记录的头信息的标记位(delete_flag)上设置true,用来表示已经删除,在查询时,如果对应记录的delete_flag为true,则表示已经被删除,就不会返回数据。
以上判断规则从 Undo Log版本链最新的行数据,逐行对比,直到找到匹配的数据,否则查询完未匹配上,则返回 NULL。
快照读:是指在一个事务中,读取的数据版本是在事务开始时已经存在的数据版本,而不是最新的数据版本。这种读取方式提供了事务在执行期间看到的数据视图的一致性,select 查询就是快照读。
当前读:是指在事务中读取最新的数据版本,以下几种操作都是快照读:
undo log 和 redo log 是相互协作的,共同保证了事务的ACID特性。
MVCC就是为快照读而生的,维护不同的快照版本,使得不同事务的读-写操作不会冲突,实现多版本并发控制,从而提高性能。
MVCC 主要应用于 InnoDB 引擎中的 RC 事务隔离级别和 RR 隔离级别,其中 RC 隔离级别每次快照读都会生成一个新的 Read View,而 RR 隔离级别只在第一次快照读时生成 Read View,之后会复用 Read View,从而解决了(部分)幻读问题。
MVCC是一种用于解决数据库并发问题的乐观锁技术,多版本并发控制通过保存数据在某个时间点的快照来实现。换句话说,读操作不会阻塞写操作,写操作也不会阻塞读操作,以此来提高数据库性能。在每次对数据的操作,都在undo log版本链中进行。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。