全网最全一篇数据库 MVCC 详解,不全你打我,java 程序员进阶路线
当前读、快照读都是什么鬼
============
什么是 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_id、db_roll_pointer、db_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 的数据进行恢复。
《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
用于 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 时“系统正处于活跃事务最小版本号”
言尽于此,完结
无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。
第一,设计模式能让专业人之间交流方便,如下:
程序员 A:这里我用了 XXX 设计模式
程序员 B:那我大致了解你程序的设计思路了
第二,易维护
项目经理:今天客户有这样一个需求…
程序员:明白了,这里我使用了 XXX 设计模式,所以改起来很快
第三,设计模式是编程经验的总结
程序员 A:B,你怎么想到要这样去构建你的代码
程序员 B:在我学习了 XXX 设计模式之后,好像自然而然就感觉这样写能避免一些问题
第四,学习设计模式并不是必须的
程序员 A:B,你这段代码使用的是 XXX 设计模式对吗?
程序员 B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的
从设计思想解读开源框架,一步一步到 Spring、Spring5、SpringMVC、MyBatis 等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了 23 种设计模式,整理的文件如下图一览无余!
搜集费时费力,能看到此处的都是真爱!
评论