数据库18mysql事务和MVCC

鉴于 MyISAM 引擎不支持事务,支持事务的引擎只有 InnoDB,所以下面关于事务的讲解都是基于 InnoDB引擎。

InnoDB如何实现事务

在 InnoDB引擎中实现事务最重要的东西就是日志文件,保证事务的四大特性主要依靠这两大日志:
redo log :保证事务持久性
undo log:回滚日志,保证事务原子性
两大日志系统分别保证了持久性和原子性,另外还有两大特性是通过什么来保证的呢?
一致性 和 隔离性 是通过 MVCC 机制 和 锁机制来一起控制。

典型的事务操作会遵循如下流程:

1
2
3
start transaction;
...... # do your business
commit;

start transaction 标识事务的开始,直到遇到 commit 才会提交事务。在该事务过程中如果出现问题,会自动调用 rollback 逻辑回滚该事物已完成的 sql。

保证原子性的关键技术-undo log

对于事务的原子性来说,该事务内所有操作要么全部成功要么全部失败就是事务的原子性。
全部成功这个毋庸置疑,如果中间突然失败,原子性该如何保证呢?是否该回滚当前已经执行成功的操作。
InnoDB 提供了一种日志:undo log,它有两个作用:提供 回滚 和 多个行版本控制(MVCC)。
比如一条 delete 操作在 undo log 中会对应一条 insert 记录,反之亦然。当 update 操作时,它会记录一条相反的 update 记录。
当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。
有时候应用到行版本控制的时候,也是通过 undo log 来实现的:当读取的某一行被其他事务锁定时,它可以从 undo log 中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。

undo log 的存储方式
InnoDB 存储引擎对 undo log 的管理采用段的方式。rollback segment 称为回滚段,每个回滚段中有 1024 个 undo log slot 。
在以前老版本,只支持 1 个 rollback segment,这样就只能记录 1024 个 undo log slot。后来 MySQL5.5 可以支持 128 个 rollback slot,即支持 128 * 1024 个 undo log 操作。

undo log 的删除时机
undo log 文件的个数是有限制的,所以不用无限堆积日志文件。undo log 记录的是当前事务操作的反向记录,理论上当前事务结束,undo log 日志就可以废弃。上面也提到过的多版本并发控制机制在隔离级别为 repeatable read 的时候事务读取的数据都是该事务最新提交的版本,那么只要该事务不结束,行版本记录就不能删除。

另外不同的 sql 语句对应的 undo log 类型也不一样,比如:
insert 语句:因为 insert 操作本身只对该事务可见,事务提交之前别的连接是看不到的,所以 insert 操作产生的 undo log 日志在事务提交之后会马上直接删除,后续不会再被别的功能使用。
update / delete 语句:delete 操作在事务中并不会真的先删除数据,而是将该条数据打上 “delete_bit” 标识,后续的删除操作是在事务提交后的 purge 线程独立操作。这两种操作产生的 undo log 日志都可以用反向的 update 来代替,这种操作上面说过 MVCC 机制可能会用上,所以就不能在事务结束之后直接删除。

在事务提交之后,也不是马上就删除该事务对应的 undo log 日志,而是将该事务对应的文件块放入到删除列表中,未来通过 purge 来删除。并且提交事务时,还会判断 undo log 分配的页是否可以重用,如果可以重用,则会分配给后面来的事务,避免为每个独立的事务分配独立的 undo log 页而浪费存储空间和性能。

持久性-redo log

redo log 即重做日志,重做日志记录每次操作的物理修改。
说 redo log 之前其实是要先说一下 binlog,不然就不知道为什么要引入 redo log。

bin log = binary log,二进制日志,它记录了除了 select 之外所有的 DDL 和 DML 语句。以事件形式记录,还包含语句所执行的消耗的时间,MySQL 的二进制日志是事务安全型的。
binlog日志有两个最重要的使用场景
mysql 主从复制: mysql replication 在 master 端开启 binlog,master 把它的二进制日志传递给 slaves 来达到 master-slave 数据一致的目的。
数据恢复: 通过 mysqlbinlog 工具来恢复数据。

binlog 日志包括两类文件
二进制日志索引文件(文件名后缀为 .index)用于记录所有的二进制文件。
二进制日志文件(文件名后缀为 .00000)记录数据库所有的 DDL 和 DML 语句事件。
binlog 文件是通过*
追加的方式写入的,可通过配置参数max_binlog_size设置每个 binlog 文件的大小,当文件大小大于给定值后,日志会发生滚动,之后的日志记录到新的文件上。
binlog 有
两种记录**模式,statement 格式的话是记 sql 语句,row 格式会记录行的内容。

持久性问题一般在发生故障的情况才会重视。在启动 MySQL 之后无论上次是否正常关闭都会进行恢复操作,我们假设现在没有 redo log 只有 binlog,那么数据文件的更新和写入 binlog 只有两种情况:
先更新数据文件,再写入 binlog;
先写入 binlog,再更新数据文件。
如果先更新数据文件,接着服务器宕机,则导致 binlog 中缺少最后的更新信息;如果先写 binlog 再更新数据则可能导致数据文件未被更新。
所以在只有 binlog 的环境中 MySQL 是不具备 crash-safe 的能力。另外一开始的 MySQL 使用 MyISAM 引擎,它只有 binlog,所以自然不支持事务。后面引入了 InnoDB 之后才开始使用另外一套日志系统- redo log 来实现 crash-safe 功能。

从一个事务的更新过程出发看看一个事务更新过程中 redo log 处于什么地位。

01,首先检查 Buffer cache 中是否存在这条数据,如果存在直接返回,如果不存在则去索引树中读取这条数据并加载到 Buffer Cache。
02,执行器拿到这条行数据之后对它执行相应的更新操作。
03,将这条待更新的行数据调用执行引擎更新到 Buffer Cache 中,同时将这个记录更新到 redo log 里面,redo log 包含两个部分的更新,更新完毕,此时 redo log 处于 prepare 的状态,然后告诉执行器,你可以提交事务。
04,执行器生成这个操作的 binlog 日志,并把 binlog 写入磁盘。
05,执行器调用引擎的提交事务接口,引擎把刚写入的 redo log 改为 commit 状态,整个事务提交完成。
这里我们注意到在 redo log 的提交过程中引入了两阶段提交。

redo log 和 binlog 的区别

redo log 是 InnoDB 引擎特有的,binlog 是MySQL server 层实现的功能,与引擎无关。
redo log 是物理日志,记录 “在某个数据页做了什么修改”;binlog 是逻辑日志,记录 sql 语句的原始逻辑,比如 “给 ID = 1 这一行的 name value set ‘xiaoming’ ”。
redo log 空间是固定的,用完之后会覆盖之前的数据;binlog 是追加写,当前文件写完之后会开启一个新文件继续写。

redo log 由两部分组成
内存中的重做日志缓冲(redo log buffer)
重做日志文件(redo log file)

一致性 和 隔离性实现-锁机制 和 MVCC

实现一致性和隔离性是保证数据准确性的关键一环,前面两个特性保证数据恢复不出问题,这两个特性要保证数据插入和读取不出问题。实现一致性和隔离性主要使用了两个机制:
锁机制
多版本并发控制

下面我们就事务会产生哪些问题,MySQL 提出什么方式来解决问题,这些方式的实现方案又是什么来讲解。

为什么要有MVCC

从我们的直观理解上来看,要实现数据库的并发访问控制,最简单的做法就是加锁访问,即读的时候不能写(允许多个线程同时读,即共享锁,S锁),写的时候不能读(一次最多只能有一个线程对同一份数据进行写操作,即排它锁,X锁)。这样的加锁访问,其实并不算是真正的并发,或者说它只能实现并发的读,因为它最终实现的是读写串行化,这样就大大降低了数据库的读写性能。加锁访问其实就是和MVCC相对的LBCC,即基于锁的并发控制(Lock-Based Concurrent Control),是四种隔离级别中级别最高的Serialize隔离级别。为了提出比LBCC更优越的并发性能方法,MVCC便应运而生。

并发下事务问题和隔离级别

从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。
事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度

隔离级别脏读不可重复读幻读
读未提交会发生会发生会发生
读提交不会发生会发生会发生
可重复读不会发生不会发生会发生
串行化不会发生不会发生不会发生

MVCC(三要素隐藏字段,undo,readview)

MVCC,Multi-Version Concurrency Control,多版本并发控制。同一份数据临时保留多版本的一种方式,进而实现并发控制,简称一致性非锁定读。
上面我们讨论过在多个事务的场景下,通过锁机制可以保证当前事务读不到未提交的事务。但是加锁也会带来坏处,那就是阻塞,只有读读之间可以并发,读写,写读,写写都不能并发操作。引入多版本机制就是为了解决这个问题,减少阻塞时间,通过这个机制,只有写写是会阻塞,其余情况都不会阻塞操作
比如我们还用 RR 隔离级别下的例子来说,事务A写了一个数据未提交,事务B读取数据,这时候是读不到A事务未提交的记录。B事务只能读到A事务未提交之前的版本。这里就使用了版本管理机制,每个连接在某个瞬间看到的是是数据库在当前的一个快照,每个事务在提交之前对其他的读者来说是不可见的
一般来说 MVCC 只在 Read Committed 和 Repeatable Read 两个隔离级别下工作Read Uncommitted 总是能读取到未提交的记录,不需要版本控制;Serializable 对所有的读取都对加锁,无需MVCC参与
MVCC 的实现,是通过保存数据在某一个时间点的快照来实现的。因此每一个事务无论执行多长时间看到的数据,都是一样的。所以 MVCC 实现可重复读。

它的实现原理主要是依赖记录中的3个隐式字段,undo日志 ,Read View 来实现的。所以我们先来看看这个三个point的概念

MVCC的实现原理_隐藏字段(相关的2个)

为了实现多版本控制,InnoDB 引擎在每一行数据中都添加了几个隐藏字段:
DB_TRX_ID:记录最近一次对本记录做(insert/upadte)的事务 ID,大小为 6 字节;
DB_ROLL_PTR:回滚指针,指向回滚段的 undo log,大小为 7 字节;
DB_ROW_ID:单调递增的行 ID,大小为 6 字节,当表没有主键索引或者非空唯一索引的时候 InnoDB 就用这个字段创聚簇索引,这个字段跟MVCC的实现没有关系
实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

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

MVCC的实现原理_事务链表

MySQL中的事务在开始到提交这段过程中,都会被保存到一个叫trx_sys的事务链表中,这是一个基本的链表结构:

事务链表中保存的都是还未提交的事务,事务一旦被提交,则会被从事务链表中摘除。

MVCC的实现原理_undo日志

undo log主要分为两种:
insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

purge
从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:
一、 比如一个有个事务插入persion表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL

二、 现在来了一个事务1对该记录的name做出了修改,改为Tom
在事务1修改该行(记录)数据时,数据库会先对该行加排他锁
然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本
拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它
事务提交后,释放锁

三、 又来了个事务2修改person表的同一个记录,将age修改为30岁
在事务2修改该行数据时,数据库也先为该行加锁
然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
事务提交,释放锁

从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)

MVCC的实现原理_Read View(读视图)

什么是Read View?
什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本

Read View关键字段

ReadView说白了就是一个数据结构,在事务开始的时候被创建。
这个数据结构中包含了3个主要的成员:ReadView{low_trx_id, up_trx_id, trx_ids},在并发情况下,一个事务在启动时,trx_sys链表中存在部分还未提交的事务,那么哪些改变对当前事务是可见的,哪些又是不可见的,这个需要通过ReadView来进行判定,首先来看下ReadView中的3个成员各自代表的意思:

low_trx_id表示该事务启动时,当前事务链表中最大的事务id编号,也就是最近创建的除自身以外最大事务编号
up_trx_id表示该事务启动时,当前事务链表中最小的事务id编号,也就是当前系统中创建最早但还未提交的事务;
trx_ids表示所有事务链表中事务的id集合。

上述3个成员组成了ReadView中的主要部分,简单图示如下:

一旦一个 Read View 被创建,这三个参数将不再发生变化,理解这点很重要,其中 min_trx_id 和 max_trx_id 分别是 trx_Ids 数组的上下界。

根据上图所示,所有数据行上DATA_TRX_ID小于up_trx_id的记录,说明修改该行的事务在当前事务开启之前都已经提交完成,所以对当前事务来说,都是可见的。
而对于DATA_TRX_ID大于low_trx_id的记录,说明修改该行记录的事务在当前事务之后,所以对于当前事务来说是不可见的。
至于位于(up_trx_id, low_trx_id)中间的事务是否可见,这个需要根据不同的事务隔离级别来确定
对于RC的事务隔离级别来说,对于事务执行过程中,已经提交的事务的数据,对当前事务是可见的,也就是说上述图中,当前事务运行过程中,trx1~4中任意一个事务提交,对当前事务来说都是可见的;
而对于RR隔离级别来说,事务启动时,已经开始的事务链表中的事务的所有修改都是不可见的,所以在RR级别下,low_trx_id基本保持与up_trx_id相同的值即可。

记录行修改的具体流程
首先当前事务对记录行加排他锁;
然后把该行数据拷贝到 undo lo g中,作为旧版本;
拷贝完毕后,修改该行的数据,并且修改记录行最新的修改事务 id ,也就是 DB_TRX_ID 为当前事务 id;
事务提交,提交前用 CAS 机制判断记录行当前最新修改的事务 id 是否发生了变化,如果没变,则提交成功;如果变了,说明存在其他事务修改了这个记录行,那么就应该回滚这个事务。也就是当前事务没有生效。

Read View记录行查询时的可见性判断算法

在 InnoDB 中创建一个新事务后,执行第一个 select 语句的时候,InnoDB 会创建一个快(readView),快照中会保存系统当前不应该被本事务看到的其他活跃事务 id 列表(即trx_ids)。当用户在这个事务中要读取某个记录行的时候,InnoDB 会将该记录行的 DB_TRX_ID 与该 ReadView 中的一些变量进行比较,判断是否满足可见性条件。
假设当前事务要读取某一个记录行,该记录行的 DB_TRX_ID(即最新修改该行的事务ID)为 trx_id,ReadView 的活跃事务列表 trx_ids 的上下界分别为 min_trx_id 和 max_trx_id。
具体的比较算法如下:
01,如果 trx_id < up_limit_id, 那么表明 “最新修改该行的事务” 在 “当前事务” 创建快照之前就提交了,所以该记录行的值对当前事务是可见的。直接标识为可见,返回true;
02,如果 trx_id >= low_limit_id, 那么表明 “最新修改该行的事务” 在 “当前事务” 创建快照之后才被创建且修改该行的,所以该记录行的值对当前事务不可见。应该通过回滚指针找到上个记录行版本,判断是否可见。循环往复,直到可见;
03,如果 up_limit_id <= trx_id < low_limit_id, 那就得通过二分查找判断 trx_id 是否在 trx_ids 列表出现过。
03.01如果出现过,说明是当前read view 中某个活跃的事务提交了,那当然是不可见的,应该通过回滚指针找到上个记录行版本,判断是否可见,循环往复,直到可见;
03.02如果没有出现过,说明这个事务是已经提交了的,表示为可见,返回 true。
需要注意的是,新建事务(当前事务)与正在内存中 commit 的事务不在活跃事务链表中。

Read View生成原则(不同隔离级别)

RC 级别:每个快照读操作都会生成最新的 read view,所以在 RC 级别中能看到别的事务提交的记录。
RR 级别:同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个Read View。

RC,RR级别下的InnoDB快照读有什么不同?

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同
01,在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
02,即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
03,而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因
总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

一个完整案例

见,参考文献《轻松理解MYSQL MVCC 实现机制》

一张图解释MVCC

mysql的mvcc(多版本并发控制)

从上面的描述可以看到,在查询时要符合以下两个条件的记录才能被事务查询出来:

1) 删除版本号 大于 当前事务版本号,就是说删除操作是在当前事务启动之后做的。
2) 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在事务中(等于的情况)或者事务启动之前。
这样就保证了各个事务互不影响。从这里也可以体会到一种提高系统性能的思路,就是:
通过版本号来减少锁的争用。
另外,只有read-committed和 repeatable-read 两种事务隔离级别才能使用mVcc
read-uncommited由于是读到未提交的,所以不存在版本的问题
而serializable 则会对所有读取的行加锁。

其他

非显式开启事务(关闭自动提交)

MySQL 中默认采用的是自动提交的模式:

1
2
3
4
5
6
mysql > show variables like 'autocommit';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| autocomment | ON |
+------------------+-------+

自动模式下,你无需显式的输入 start transaction 作为开头和使用 commit 作为结尾来标识一个事务。每个sql 语句都会被作为一个事务提交。
当然你也可以关闭自动提交事务机制:

1
mysql > set autocommit = 0;

需要注意的是:autocommit 参数的修改指只针对当前连接,在一个连接中修改该属性并不会影响别的连接。

不被 autocommit 影响的操作

MySQL 中提供了一些不会被 autocommit 属性值所影响的特殊指令,这些指定即使在事务中执行,他们也会立刻执行而不是等到 commit 语句之后再提交,这些特殊指令包括:DDL(create table / drop table / alter table)、lock tables等等。

幻读

在RR级别下,MySQL的InnoDB引擎通过MVCC(多版本并发控制,Multi-Version Concurrency Control)机制,解决了脏读和不可重复读的问题,同时无锁的设计极大提高了其性能。但是MVCC不能解决幻读的问题。

在网络上,关于MySQL的RR级别,能否解决幻读问题其实有一定的争议,有两种常见的说法是RR可以解决幻读问题以及RR可以部分解决幻读问题,但不能完全解决。在讨论能否解决幻读问题(Phantom Rows)之前,首先要清楚什么是幻读问题。在ANSI SQL-92标准和MySQL的官方文档中,对幻读都有着明确的定义。

1
2
3
4
5
# ANSI SQL-92 (P1和P2分别是Dirty read和Non-repeatable read)
P3 ("Phantom"): SQL-transaction T1 reads the set of rows N that satisfy some <search condition>. SQL-transaction T2 then executes SQL-statements that generate one or more rows that satisfy the <search condition> used by SQL-transaction T1. If SQL-transaction T1 then repeats the initial read with the same <search condition>, it obtains a different collection of rows.

# MySQL8.0 doc
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

可以看到,phantom rows所指是在返回结果是一个集合(set)的时候,前后两次查询返回结果不一致的问题。MySQL文档中的举例为:

1
SELECT * FROM child WHERE id > 100 FOR UPDATE;

假设原有id为90和102的记录,事务T1中执行该查询,T2执行插入id为101的记录并提交,T1再次执行该查询,两次结果不一致,即发生了幻读问题。个人认为,幻读类似于范围查询的不可重读问题。

什么是当前读和快照读?

在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读和快照读?
当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现

当前读,快照读和MVCC的关系

准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念
而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现
要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的,具体可以看下面的MVCC实现原理

参考

浅谈Innodb中的幻读以及对行级锁的实现:https://blog.csdn.net/weixin_44339331/article/details/108183619
一篇文章把 InnoDB 的事务机制给你弄的明明白白:https://blog.csdn.net/xingduan5153/article/details/108618501
【MySQL笔记】正确的理解MySQL的MVCC及实现原理:https://blog.csdn.net/SnailMann/article/details/94724197#t1
正确的理解MySQL的MVCC及实现原理:https://blog.51cto.com/u_12182612/2486731
轻松理解MYSQL MVCC 实现机制:https://blog.csdn.net/whoamiyang/article/details/51901888
MySQL MVCC实现:https://mp.weixin.qq.com/s/57DBCnCqmoY6Ia_xFXmfdQ?
懵了!女朋友突然问我MVCC实现原理:https://my.oschina.net/u/3828348/blog/5008685
Mysql事务隔离原理MVCC\LBCC:https://blog.csdn.net/renshengrushui/article/details/118312628

数据库系列
数据库01mysql常用操作速查
数据库02mongodb异常错误
数据库03mongodb占用磁盘空间过大
数据库04sqlite转mysql
数据库05redis常用命令整理
数据库06redis事务
数据库07redis分布式锁
数据库08redis其他
数据库09mysql常用查询实例
数据库10mysql之坑
数据库11mysql之坑null专题
数据库12经验之谈
数据库13mysql执行计划
数据库14mysql的redolog与binlog
数据库15mysql报错2006
数据库16mysql之初始密码
数据库17mysql锁机制
数据库18mysql事务和MVCC

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×