MySQL InnoDB MVCC机制

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
日志服务 SLS,月写入数据量 50GB 1个月
简介: MySQL InnoDB MVCC机制

对于普通select来说, InnoDB使用MVCC保证了事务隔离. 同一事务的两次相同查询语句都是同样结果, 其他事务修改记录不影响当前事务, 特殊情况是会看到同一事务中先前语句所做的更新, 所以对于普通select(快照读)来说, MVCC是解决了脏读/不可重复读/幻行的; 而对于当前读(锁定读)来说, InnoDB提供了GAP/Next-Key/Index-Record等锁算法保证隔离性, 这个后续再说.

当前MySQL8.0.28

那么InnoDB是如何实现MVCC的

1. InnoDB默认以B+Tree结构组织索引记录, 主键是聚集索引, 叶子节点存储真正的索引记录, 而索引记录会多出两列与MVCC有关的隐藏列, 当使用 SQL 删除行时,不会立即从数据库中物理删除它. 对于更新操作, 更新前的记录同样会被保留, 只是标记删除. InnoDB只有在清除undolog时(当系统里没有比这个回滚日志更早的ReadView的时候),才会物理删除相应的行及其索引记录

  1. DATA_TRX_ID: 数据行所属事务id, 最近更新该行的事务id
  2. DATA_ROLL_PTR: 指向undolog的指针

2. InnoDB使用ReadView(读视图)来辅助判断当前事务是否能读取该行数据版本, ReadView主要包含如下属性

  1. m_ids: 生成ReadView时, 当前活跃所有的事务ID(事务ID自增)
  2. min_trx_id: 当前活跃的m_ids中最小的事务ID
  3. max_trx_id: 生成ReadView时,最大的事务ID,并不是m_ids中最大的ID
  4. creator_trx_id: 该ReadView在哪个事务创建的

1.如果被访问版本(当前最新记录或undolog中的记录)的 data_trx_id 小于min_trx_id,说明生成该版本的事务在 ReadView 生成前就已经提交了,那么该版本可以被当前事务访问 2.如果被访问版本的 data_trx_id大于max_trx_id,说明生成该版本数据的事务在生成 ReadView后才生成,那么该版本不可以被当前事务访问 3.如果被访问版本的 data_trx_id属性值在 max_trx_id和min_trx_id之间(包含),那就需要判断一下 trx_id 的值是不是在 m_ids 列表中。如果在,说明创建 ReadView 时生成该版本所属事务还是活跃的,因此该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问 4.被访问版本的事务id等于当前事务id

mysql8.0.28源码

[[nodiscard]] bool changes_visible(trx_id_t id,                                      const table_name_t &name) const {     ut_ad(id > 0);     if (id < m_up_limit_id || id == m_creator_trx_id) {       return (true);     }     check_trx_id_sanity(id, name);     if (id >= m_low_limit_id) {       return (false);     } else if (m_ids.empty()) {       return (true);     }     const ids_t::value_type *p = m_ids.data();     return (!std::binary_search(p, p + m_ids.size(), id));   }

3. 在MySQL中, 实际上每条记录在更新的时候都会同时记录一条回滚操作到undolog(undolog默认在mysql的data文件夹中)中. 记录上的最新值, 通过回滚操作, 都可以得到前一个状态的值.

比如id=880这行记录

id

name

age

value

uni

DATA_TRX_ID

DATA_ROLL_PTR

880

Barb Dwyer

10

42

52

100

执行update user set age = 12 where id = 880;后, undolog中存储update执行前的记录

更新后的记录

id

name

age

value

uni

DATA_TRX_ID

DATA_ROLL_PTR

880

Barb Dwyer

12

42

52

200

0xAA

undolog记录

id

name

age

value

uni

DATA_TRX_ID

DATA_ROLL_PTR

880

Barb Dwyer

10

42

52

100

指向下一个undolog的地址

若后续事务多次修改该行, 则undolog中会有多条记录

再执行update user set age = 14 where id = 880;

undolog记录

addr

id

name

age

value

uni

DATA_TRX_ID

DATA_ROLL_PTR

0xBB

880

Barb Dwyer

12

42

52

200

0xAA

0xAA

880

Barb Dwyer

10

42

52

100

当系统里没有比这个回滚日志更早的ReadView的时候会删除回滚日志, 即该undolog不再被需要, 但insert的undolog日志在事务结束后可以立即删除, 因为如果某个事务ID=100新增了一条记录,那么在这个事务版本之前这个记录是不存在的,也就是这条数据要么就是事务100提交,然后就存在这条数据了,事务100没有提交,这条数据就是null, 也就不需要多版本的冗余, 所以事务提交就可以直接删除insert的undo log.

4. 对于二级索引(非聚簇索引), MVCC对二级索引的处理方式与对聚集索引的处理方式不同. 聚集索引中的记录立即更新(内存中的记录),它们的隐藏列指向undolog记录位置,可以从中重建早期版本的记录。与聚集索引记录不同,二级索引记录不包含隐藏的系统列,也不会立即更新. 当二级索引列被更新时,旧二级索引记录被删除标记,新记录被插入,并且被删除标记的记录最终被清除(当该记录不再被需要时), 当二级索引记录被标记删除或二级索引页面被更新时,则在聚集索引中查找数据库记录. 但是,如果启用了 索引条件下推 (ICP)优化,并且WHERE条件可以仅使用索引中的字段来过滤数据,则 MySQL 服务器仍会将这部分WHERE条件下推到存储引擎. 如果没有找到匹配的记录,则无需在聚集索引中查找。如果找到匹配的记录,即使记录被标记删除,也会在聚集索引中查找记录

5. RR与RC的区别就在于, RC每次查询都生成一个最新的ReadView, 而RR只生成一个

以下是一些较特殊的情况

Step

Session A

Session B

1

begin;

2

begin; update user set age = 12 where id = 880; commit;

3

select * from user where id = 880; +-----+------------+-----+-------+-----+ | id | name | age | value | uni | +-----+------------+-----+-------+-----+ | 880 | Barb Dwyer | 12 | 42 | 52 |

4

commit;

RR隔离级别下的一致性读,不是以begin开始的时间点作为快照建立时间点,而是以第一条select语句的时间点作为快照建立的时间点.

Step

Session A

Session B

1

begin;

2

select * from user where name = "update"; Empty set

3

begin; update user set name = "update" where value = 42; commit;

4

select * from user where name = "update"; Empty set

5

select * from user where name = "update" for update +-----+------------+-----+-------+-----+ |id |name |age |value |uni | +-----+------------+-----+-------+-----+ |880 |update|12 |42 |52 | +-----+------------+-----+-------+-----+

6

commit;

select ... for update使用当前读, 会读取最新版本的数据

Step

Session A

Session B

Session C

1

begin; select * from user where name = 'tom'; +-----+------------+-----+-------+-----+ |id |name |age |value |uni | +-----+------------+-----+-------+-----+ |990 | tom | 10 | 42 | 52 |

2

begin; select * from user where name = "update"; Empty set

3

begin; update user set name = "update" where id = 990; commit;

4

select * from user where name = "update"; Empty set

5

update user set age = 99 where name = "update";

6

select * from user where name = "update" +-----+------------+-----+-------+-----+ |id | name |age |value |uni | +-----+------------+-----+-------+-----+ |990| update | 99 |42 |52 | +-----+------------+-----+-------+-----+

select * from user where name = 'tom'; +-----+------------+-----+-------+-----+ |id |name |age |value |uni | +-----+------------+-----+-------+-----+ |990 |tom |10 |42 |52 |

7

commit;

commit;

若当前事务修改了其他事务修改过的行, 则该事务后续使用普通select也能看到其他事务更新的数据.

会话A一开始查询不到name=update的记录,

接着会话B在第三步修改了将id=990这行记录的name修改为update, 生成了一条undolog记录, 同时也将990这行的事务id和undolog指针记录更新了. 根据ReadView的定义, 会话B的事务id明显比会话A创建时最大的事务id还要大,

所以会话A第四步再次查询, 仍然查询不到最新的修改.

但会话A第五步, 使用了update语句修改990这行的age字段, update使用当前读, 所以能够查询到name=update的记录, 事务A把字段age更新为99, 也将990这行的事务id和undolog指针记录更新为当前事务id和当前事务产生的undolog位置

会话A第六步再调用select查询, 查询到了990这行的name符合条件, 同时该行的事务id也符合ReadView可见性的定义, 事务列的数据与当前事务一致, 所以就可以查询到记录

同是第六步, 会话C再次调用查询, name和age仍为事务开始时所查询到的. 且因为name这行只是被标记删除, 所以name这个条件仍可以用于查询. 只是InnoDB发现当前行的事务id已经被更新过, 所以再去查询undolog中的版本记录, 最终根据会话C开启事务时创建的ReadView返回会话B修改后生成的数据版本

ref: https://www.cnblogs.com/rongdi/p/13378892.html

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
0
0
1
分享
相关文章
MySQL底层概述—10.InnoDB锁机制
本文介绍了:锁概述、锁分类、全局锁实战、表级锁(偏读)实战、行级锁升级表级锁实战、间隙锁实战、临键锁实战、幻读演示和解决、行级锁(偏写)优化建议、乐观锁实战、行锁原理分析、死锁与解决方案
129 24
MySQL底层概述—10.InnoDB锁机制
MySQL底层概述—5.InnoDB参数优化
本文介绍了MySQL数据库中与内存、日志和IO线程相关的参数优化,旨在提升数据库性能。主要内容包括: 1. 内存相关参数优化:缓冲池内存大小配置、配置多个Buffer Pool实例、Chunk大小配置、InnoDB缓存性能评估、Page管理相关参数、Change Buffer相关参数优化。 2. 日志相关参数优化:日志缓冲区配置、日志文件参数优化。 3. IO线程相关参数优化: 查询缓存参数、脏页刷盘参数、LRU链表参数、脏页刷盘相关参数。
104 12
MySQL底层概述—5.InnoDB参数优化
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
1.请解释什么是MVCC,它在数据库中的作用是什么? 2.在MySQL中,MVCC是如何实现的?请简述其工作原理。 3.MVCC是如何解决读-写和写-写冲突的? 4.在并发环境中,当多个事务同时读取同一行数据时,MVCC是如何保证每个事务看到的数据版本是一致的? 5.MVCC如何帮助提高数据库的并发性能?
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
MySQL底层概述—4.InnoDB数据文件
本文介绍了InnoDB表空间文件结构及其组成部分,包括表空间、段、区、页和行。表空间是最高逻辑层,包含多个段;段由若干个区组成,每个区包含64个连续的页,页用于存储多条行记录。文章还详细解析了Page结构,分为通用部分(文件头与文件尾)、数据记录部分和页目录部分。此外,文中探讨了行记录格式,包括四种行格式(Redundant、Compact、Dynamic和Compressed),重点介绍了Compact行记录格式及其溢出机制。最后,文章解释了不同行格式的特点及应用场景,帮助理解InnoDB存储引擎的工作原理。
MySQL底层概述—4.InnoDB数据文件
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
docker拉取MySQL后数据库连接失败解决方案
通过以上方法,可以解决Docker中拉取MySQL镜像后数据库连接失败的常见问题。关键步骤包括确保容器正确启动、配置正确的环境变量、合理设置网络和权限,以及检查主机防火墙设置等。通过逐步排查,可以快速定位并解决连接问题,确保MySQL服务的正常使用。
151 82
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)
Docker Compose V2 安装常用数据库MySQL+Mongo
以上内容涵盖了使用 Docker Compose 安装和管理 MySQL 和 MongoDB 的详细步骤,希望对您有所帮助。
222 42
目录
目录
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等