欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > MySQL的 MVCC详解

MySQL的 MVCC详解

2025/2/8 5:02:44 来源:https://blog.csdn.net/weixin_73093777/article/details/145479944  浏览:    关键词:MySQL的 MVCC详解

MVCC是多版本并发控制,允许多个事务同时读取和写入数据库,而无需互相等待,从而提高数据库的并发性能。

在 MVCC 中,数据库为每个事务创建一个数据快照。每当数据被修改时,MySQL不会立即覆盖原有数据,而是生成新版本的记录。每个记录都保留了对应的版本号或时间戳。

依赖实现:隐藏字段(rowId+trxId)+undolog+readview

MVCC本质是采用乐观锁思想, 非阻塞并发读 ,而这个读指的就是快照读 , 而非当前读

  • 当前读就是加锁操作,是悲观锁的实现。当前读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录

SELECT ... FOR UPDATE           #为查询到的每一条记录添加排他锁  
SELECT ... LOCK IN SHARE MODE   #为查询到的每一条记录添加共享锁
  • 快照读又叫一致性读,读的是快照数据,可能不是最新数据,而是历史数据,不加锁的 简单select都属于快照读

ReadView

隐藏字段和 undo 版本链决定是时返回的数据,具体返回哪个数据版本,由这个ReadView控制,即 进行快照读操作时产生的读视图

设计思路

这个ReadView其实就是维护了一个集合,主要包含4个部分,分别如下:

  • creator_trx_id,创建这个 Read View 的事务 ID

  • trx_ids: 表示在生成 ReadView 时当前系统中活跃的读写事务的事务id列表

  • up_limit_id:活跃事务中最小的事务 ID

  • low_limit_id:生成 ReadView 时,应该分配给下一个事务的 ID 值。(最大事务 ID +1)

规则

有了ReadView,那么在访问某条记录时,只需要按照下边的步骤查看记录的对应快照版本

整体操作流程

比如查询一条记录的时候,系统如何通过MVCC找到它:

  1. 首先获取事务自己的版本号trxId,也就是事务 ID;

  2. 获取 当前的系统的 ReadView ,然后与 ReadView 中的事务版本号进行比较;

  3. 如果不符合 ReadView 规则,就从 Undo Log版本链中依次往下获取该记录的历史快照事务ID进行按照上面规则比较;

  4. 最后返回符合规则的数据。若最后一个版本都不可见,说明该条记录对于目前该事务完全不可见(没提交),也就查不到该条记录

读已提交和可重复读

在隔离级别为读已提交时,一个事务中的每一次SELECT查询都会重新获取一次Read View。

这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况

当隔离级别为可重复读的时候,解决了不可重复读,并通过 间隙锁+MVCC 解决了大部分的幻读问题

大部分幻读解决:

  • 因为可重复读复用,第一次readView,所以事务的查询快照结果是一样,不会平白无故多出数据来,通过readView解决了快照读的幻读

  • 间隙锁的话解决了当前读的幻读问题,防止其他事务在这个间隙间插入新的记录。

例外情况:

  • 比如,如果两个事务,事务1先进行快照读,然后事务2插入了一条记录并提交,在事务1中进行了当前读之后,再进行快照读也会发生幻读。

因为此时一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的查询都会复用这个 Read View

举例说明
读已提交

每次读取数据前都生成一个ReadView

-- 现在有两个 事务id 分别为 10 、 20 的事务在执行
​
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
​
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...

此刻,表student 中 id1 的记录得到的版本链表如下所示:

假设现在有一个使用 READ COMMITTED 隔离级别的事务30开始执行:

# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三',因为王五李四事务是活跃的,所以查undolog 读取历史快照张三

之后,我们把 事务id10 的事务提交一下:

# Transaction 10
COMMIT;

然后再到 事务id20 的事务中更新一下表 studentid1 的记录:

# Transaction 20
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student中 id1的记录的版本链就长这样:

然后再到事务30中继续查找这个 id 为 1 的记录

# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1;   # 得到的列name的值为'王五'

所以看到,两次读取的记录并不一样,所以不可重复嘛

可重复读

只会在第一次执行查询语句时生成一个 ReadView ,之后的查询就不会重复生成

得到的列name的值仍为'张三',因为复用第一次的readview,所以上图还是认为王五没有提交

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com