数据库如何通过 MVCC 实现隔离性 一、MVCC 是什么MVCC 是一种多版本 快照读的并发控制机制用来实现隔离性并通过不同的 ReadView 策略支撑不同隔离级别同时保证读写不阻塞。二、MVCC的实现1、隐藏字段每行记录的“身份证”InnoDB 在每行记录上除了你定义的字段还偷偷加了几个隐藏字段而与 MVCC 相关的字段只有前2项隐藏字段含义作用DB_TRX_ID最后一次修改该行的事务 ID判断版本可见性DB_ROLL_PTR回滚指针指向 undo log 中的上一个版本串联版本链2、Undo Log版本链历史版本仓库每次修改数据旧值都会被写进 undo log并通过DB_ROLL_PTR回滚指针串成一条版本链。特点链首是最新版本。越往后越旧。每个版本都带trx_id用于可见性判断。3、ReadView可见性判断容器这里先说一下快照读和当前读快照读是什么 读历史版本快照不是读最新 普通 SELECT 不加锁 靠 MVCC 实现ReadView undo log 版本链 对比 快照读 → 读历史、不加锁、并发高 当前读 → 读最新、加锁、保证强一致ReadView读视图是事务在执行快照读时生成的一个对象里面记录了生成这一刻数据库的活跃事务信息用来判断版本链上的哪个版本对当前事务可见。ReadView 的四个字段字段含义m_ids生成 ReadView 时当前活跃未提交的事务 ID 列表min_trx_idm_ids 中的最小值max_trx_id下一个将要分配的事务 ID当前最大 1creator_trx_id创建该 ReadView 的事务 ID4、可见性判断算法规则①自己改的可见事务 A 改了数据虽然还没提交但 A 自己当然要能看到自己的修改→ creator_trx_id trx_id → 可见规则②早于 ReadView 生成时刻的可见如果 trx_id min_trx_id说明这个事务在 ReadView 生成之前就已经不在活跃列表了→ 意味着它早就提交了 → 可见规则③晚于 ReadView 生成时刻的不可见如果 trx_id max_trx_id说明这个事务是在 ReadView 生成之后才开启的→ 快照不该看到未来的事务 → 不可见规则④介于两者之间的看是否活跃在 min 和 max 之间的可能是- 已提交的- 还活跃的怎么区分看它是否在 m_ids 里在 m_ids → 当时还活跃 → 不可见不在 m_ids → 当时已提交 → 可见不可见怎么办当前版本不可见↓沿 DB_ROLL_PTR回滚指针 找上一个版本↓再判断直到找到可见版本↓若都不可见说明该行对当前事务不存在三、隔离级别与MVCC的关系隔离级别实现方式用 MVCC 吗READ UNCOMMITTED读未提交直接读最新不加过滤❌READ COMMITTED读已提交MVCC每次新建 ReadView✅REPEATABLE READ不可重复读MVCC复用 ReadView✅SERIALIZABLE串行化加锁串行执行❌四、MVCC 如何实现 RC读未提交和 RR(不可重复读快照读时会沿着版本链逐个判断一旦某个版本满足当前 ReadView 的可见性规则就读这个版本并返回。MVCC 靠同一个版本链 不同的 ReadView 生成时机实现了 RC 和 RR 的差异。维度RC读已提交RR可重复读ReadView 生成时机每次 SELECT 都新建事务首次 SELECT 生成之后复用快照范围每次读都是最新快照全程一个固定快照能否读到别人提交✅ 能提交后下次读就能看到❌ 不能快照固定解决的问题脏读脏读 不可重复读五、事务中的隔离性是如何保证的事务的隔离性是由锁和 MVCC实现的。其中MVCC的意思是多版本并发控制指维护一个数据的多个版本使得读写操作没有冲突。它的底层实现主要分为三个部分隐藏字段、undo log 日志、ReadView 读视图。隐藏字段在 MySQL 中给每个表都设置了隐藏字段一个是trx_id事务 id记录每一次操作的事务 id是自增的另一个是roll_pointer回滚指针指向上一个版本的事务版本记录地址。undo log主要作用是记录回滚日志存储老版本数据。在内部会形成一个版本链在多个事务并行操作某一行记录时记录不同事务修改数据的版本通过roll_pointer指针形成一个链表。ReadView解决的是一个事务查询选择版本的问题在内部定义了一些匹配规则和当前的一些事务 id判断该访问哪个版本的数据。不同的隔离级别快照读是不一样的最终的访问结果也不一样RC 隔离级别每一次执行快照读时生成 ReadView。RR 隔离级别仅在事务中第一次执行快照读时生成 ReadView后续复用。