全网最全一篇数据库MVCC详解,不全你打我

微信扫一扫,分享到朋友圈

全网最全一篇数据库MVCC详解,不全你打我

什么是MVCC

全称Multi-Version Concurrency Control,即 多版本并发控制 ,主要是为了提高数据库并发性能 。以下文章都是围绕InnoDB引擎来讲,因为myIsam不支持事务。

同一行数据平时发生读写请求时,会 上锁阻塞 住。但mvcc用更好的方式去处理读—写请求,做到在发生读—写请求冲突时 不用加锁

这个读是指的 快照读 ,而不是 当前读 ,当前读是一种加锁操作,是 悲观锁

那它到底是怎么做到读—写 不用加锁 的, 快照读当前读 又是什么鬼,跟着你们的 贴心老哥 ,继续往下看。

当前读、快照读都是什么鬼

什么是MySQL InnoDB下的当前读和快照读?

当前读

它读取的数据库记录,都是 当前最新版本 ,会对当前读取的数据进行 加锁 ,防止其他事务修改数据。是 悲观锁 的一种操作。

如下操作都是当前读:

  • select lock in share mode (共享锁)

  • select for update (排他锁)

  • update (排他锁)

  • insert (排他锁)

  • delete (排他锁)

  • 串行化事务隔离级别

快照读

快照读的实现是基于 多版本 并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前 历史版本 的数据。

如下操作是快照读:

  • 不加锁的select操作(注:事务级别不是串行化)

快照读与mvcc的关系

MVCCC 是“维持一个数据的多个版本,使读写操作没有冲突”的一个 抽象概念

这个概念需要具体功能去实现,这个具体实现就是 快照读 。(具体实现下面讲)

听完 贴心老哥 的讲解,是不是瞬间 茅厕顿开

数据库并发场景

  • 读-读 :不存在任何问题,也不需要并发控制

  • 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读

  • 写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

MVCC解决并发哪些问题?

mvcc用来解决读—写冲突的无锁并发控制,就是为事务分配 单向增长时间戳 。为每个数据修改保存一个 版本 ,版本与事务时间戳 相关联

读操作 只读取 该事务 开始前数据库快照

解决问题如下:

  • 并发读-写时 :可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。

  • 解决 脏读幻读不可重复读 等事务隔离问题,但不能解决上面的 写-写 更新丢失 问题。

因此有了下面提高并发性能的 组合拳

  • MVCC + 悲观锁 :MVCC解决读写冲突,悲观锁解决写写冲突

  • MVCC + 乐观锁 :MVCC解决读写冲突,乐观锁解决写写冲突

MVCC的实现原理

它的实现原理主要是 版本链undo日志Read View 来实现的

版本链

我们数据库中的每行数据,除了我们肉眼看见的数据,还有几个 隐藏字段 ,得开 天眼 才能看到。分别是 db_trx_iddb_roll_pointerdb_row_id

  • db_trx_id

    6byte,最近修改(修改/插入) 事务ID :记录 创建 这条记录/ 最后一次修改 该记录的 事务ID

  • db_roll_pointer(版本链关键)

    7byte, 回滚指针 ,指向 这条记录上一个版本 (存储于rollback segment里)

  • db_row_id

    6byte,隐含的 自增ID (隐藏主键),如果数据表 没有主键 ,InnoDB会自动以db_row_id产生一个 聚簇索引

  • 实际还有一个 删除flag 隐藏字段, 记录被 更新删除 并不代表真的删除,而是 删除flag 变了

如上图, db_row_id 是数据库默认为该行记录生成的 唯一隐式主键db_trx_id 是当前操作该记录的 事务ID ,而 db_roll_pointer 是一个 回滚指针 ,用于配合 undo日志 ,指向上一个 旧版本

每次对数据库记录进行改动,都会记录一条 undo日志 ,每条undo日志也都有一个 roll_pointer 属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些 undo日志都连起来串成一个链表 ,所以现在的情况就像下图一样:

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一个 链表 ,我们把这个链表称之为 版本链 ,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。

undo日志

Undo log 主要用于 记录 数据被 修改之前 的日志,在表信息修改之前先会把数据拷贝到 undo log 里。

事务 进行 回滚时 可以通过undo log 里的日志进行 数据还原

Undo log 的用途

  • 保证 事务 进行 rollback 时的 原子性和一致性 ,当事务进行 回滚 的时候可以用undo log的数据进行 恢复

  • 用于MVCC 快照读 的数据,在MVCC多版本控制中,通过读取 undo log历史版本数据 可以实现 不同事务版本号 都拥有自己 独立的快照数据版本

undo log主要分为两种:

  • insert undo log

    代表事务在insert新记录时产生的undo log , 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

  • update undo log(主要)

    事务在进行update或delete时产生的undo log ; 不仅在事务回滚时需要,在快照读时也需要;

    所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

Read View(读视图)

事务进行 快照读 操作的时候生产的 读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个 快照

记录并维护系统当前 活跃事务的ID (没有commit,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被 本事务 看到的 其他事务id列表

Read View主要是用来做 可见性 判断的, 即当我们 某个事务 执行 快照读 的时候,对该记录创建一个Read View读视图,把它比作条件用来判断 当前事务 能够看到 哪个版本 的数据,既可能是当前 最新 的数据,也有可能是该行记录的undo log里面的 某个版本 的数据。

Read View几个属性

  • trx_ids : 当前系统活跃( 未提交 )事务版本号集合。

  • low_limit_id : 创建当前read view 时“当前系统 最大事务版本号 +1”。

  • up_limit_id : 创建当前read view 时“系统正处于活跃事务 最小版本号

  • creator_trx_id : 创建当前read view的事务版本号;

Read View可见性判断条件

  • db_trx_id < up_limit_id || db_trx_id == creator_trx_id (显示)

    如果数据事务ID小于read view中的 最小活跃事务ID ,则可以肯定该数据是在 当前事务启之前 就已经 存在 了的,所以可以 显示

    或者数据的 事务ID 等于 creator_trx_id ,那么说明这个数据就是当前事务 自己生成的 ,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以 显示 的。

  • db_trx_id >= low_limit_id (不显示)

    如果数据事务ID大于read view 中的当前系统的 最大事务ID ,则说明该数据是在当前read view 创建 之后才产生 的,所以数据 不显示 。如果小于则进入下一个判断

  • db_trx_id 是否在 活跃事务 (trx_ids)中

    • 不存在 :则说明read view产生的时候事务 已经commit 了,这种情况数据则可以 显示

    • 已存在 :则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。

MVCC和事务隔离级别

上面所讲的 Read View 用于支持 RC (Read Committed,读提交)和 RR (Repeatable Read,可重复读) 隔离级别实现

RR、RC生成时机

  • RC 隔离级别下,是每个 快照读 都会 生成并获取最新Read View

  • 而在 RR 隔离级别下,则是 同一个事务中第一个快照读 才会创建 Read View , 之后的 快照读获取的都是 同一个Read View ,之后的查询就 不会重复生成 了,所以一个事务的查询结果每次 都是一样的

解决幻读问题

  • 快照读 :通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。

  • 当前读 :通过next-key锁(行锁+gap锁)来解决问题的。

RC、RR级别下的InnoDB快照读区别

  • 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;

  • 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见

  • 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

总结

从以上的描述中我们可以看出来,所谓的MVCC指的就是在使用 READ COMMITTDREPEATABLE READ 这两种隔离级别的事务在执行普通的 SEELCT 操作时访问记录的 版本链 的过程,这样子可以使不同事务的 读-写写-读 操作 并发执行 ,从而 提升系统性能

关注微信公众号:IT 老哥

回复:Java实战项目视频教程:即可获取200G,27套实战项目视频教程

回复:Java 学习路线,即可获取最新最全的一份学习路线图

回复:Java 电子书,即可领取 13 本顶级程序员必读书籍

回复:Java 全套教程,即可领取:Java 基础、Java web、JavaEE 全部的教程,包括 spring boot 等

回复:简历模板,即可获取 100 份精美简历

微信扫一扫,分享到朋友圈

全网最全一篇数据库MVCC详解,不全你打我

一条视频涨粉4万,听视频号幕后操盘手聊如何制造爆款!

上一篇

报道称索尼将大幅下调PS5售价 以应对微软次世代Xbox的挑战

下一篇

你也可能喜欢

全网最全一篇数据库MVCC详解,不全你打我

长按储存图像,分享给朋友