八卦阵 发表于 4 天前

高频面试,请说说MySQL MVCC 事务隔离机制的实现原理

什么是MVCC?先上结论再表明!

MVCC(Multi-Version Concurrency Control)多版本并发控制,说人话就是,MySQL让你看到的数据,大概是它专门为你版本控制后的结果,而不是数据库里现实存储的当前值。
听起来像不像git的版本控制?没错,异曲同工!
想象一下,小明和小红同时查询一张表,但小红在查询的过程中,小明修改了表中的数据。如果没有MVCC,小红看到的数据大概前后不划一 —— 这就破坏了事务的隔离性。有了MVCC,小红看到的是她开始查询时的数据快照,不受小明修改的影响。
MVCC解决了什么题目?

直奔主题,MVCC解决了数据库并发访问的三大题目:

[*]脏读:读到了别人未提交的数据(想象你看到同事写了一半的代码,结果他最后放弃提交了)
[*]不可重复读:同一事务内,读取同一数据两次,结果不一样(想象你上午看股票是10元,下午再看酿成11元了)
[*]幻读:同一事务内,按同样条件查询两次,后一次比前一次多了或少了(想象你查询未婚青年名单,查完1分钟后再查,结果多了几个人)
在不同隔离级别下,MVCC的表现也有所不同:


[*]READ UNCOMMITTED:不使用MVCC(最低隔离级别,啥也不解决)
[*]READ COMMITTED:每次SELECT都天生一个新的Read View(解决脏读)
[*]REPEATABLE READ:在事务开始时创建一次Read View(解决脏读、不可重复读)
[*]SERIALIZABLE:不使用MVCC,串行实行(最高隔离级别,全解决,但性能最差)
MySQL的InnoDB默认隔离级别是REPEATABLE READ,也就是可重复读。
MVCC是怎么实现的?揭秘技术内幕

潜伏字段:数据库的暗黑操作

你大概不知道,InnoDB偷偷在我们的表上加了三个潜伏字段:

[*]DB_TRX_ID:记载最后修改该行数据的事务ID
[*]DB_ROLL_PTR:回滚指针,指向该行的undo log
[*]DB_ROW_ID:行ID(如果没有主键,InnoDB会用它作为主键)
https://i-blog.csdnimg.cn/direct/13dc9bd66bd94b14b22f6bab90b7ed9f.png
核心机制:Undo Log与Read View

MVCC的两大核心武器就是Undo Log(回滚日记)和Read View(读视图):

[*]Undo Log:记载数据被修改前的值,用于回滚和MVCC
[*]Read View:事务实行快照读时产生的视图,决定哪些数据对当前事务可见
Undo Log的工作原理

当事务修改数据时,InnoDB会:

[*]将修改前的数据记载到Undo Log
[*]通过DB_ROLL_PTR指向该Undo Log
[*]更新DB_TRX_ID为当前事务ID
如许就形成了一个版本链:
https://i-blog.csdnimg.cn/direct/31a1907a016a426d8ba371e932296226.png
Read View的工作原理

Read View是事务在快照读(普通SELECT)时天生的读视图,包含以下紧张信息:


[*]m_ids:在天生Read View时,当前系统中活跃的事务ID列表
[*]min_trx_id:在天生Read View时,系统中活跃的最小事务ID
[*]max_trx_id:在天生Read View时,系统应该分配给下一个事务的ID
[*]creator_trx_id:天生该Read View的事务ID
当事务必要读取记载时,会用记载的DB_TRX_ID跟Read View举行比力,判断该版本对当前事务是否可见:
https://i-blog.csdnimg.cn/direct/6ebe2a07e24045f8822ce0b0cdd9e4d2.png
// 判断记录是否可见的伪代码
boolean isVisible(long trx_id) {
    // 如果是当前事务修改的,可见
    if (trx_id == creator_trx_id) {
      return true;
    }
   
    // 如果是在创建ReadView之后才开启的事务修改的,不可见
    if (trx_id >= max_trx_id) {
      return false;
    }
   
    // 如果是在创建ReadView之前就已经提交的事务修改的,可见
    if (trx_id < min_trx_id) {
      return true;
    }
   
    // 如果是活跃事务列表中的事务修改的,不可见
    if (m_ids.contains(trx_id)) {
      return false;
    }
   
    // 其他情况,可见(已提交的事务)
    return true;
}
读操作的分类:当前读与快照读

MVCC中有两种读取方式:

[*] 快照读(Snapshot Read):读取的是记载的可见版本,大概是历史数据,不加锁
SELECT * FROM table WHERE id = 1;

[*] 当前读(Current Read):读取的是记载的最新版本,会加锁
-- 以下操作都是当前读
SELECT * FROM table WHERE id = 1 LOCK IN SHARE MODE;
SELECT * FROM table WHERE id = 1 FOR UPDATE;
INSERT INTO table VALUES (1, 2, 3);
DELETE FROM table WHERE id = 1;
UPDATE table SET col = 1 WHERE id = 1;

实操案例:MVCC如何在现实中工作

来看个详细例子,假设我们有个账户表:
CREATE TABLE account (
    id INT PRIMARY KEY,
    name VARCHAR(20),
    balance INT
);

INSERT INTO account VALUES (1, '张三', 1000);
假设有两个事务并发实行:
/* 初始数据:account表中id=1的记录,balance=1000 */

-- 事务A
BEGIN;                                    -- 事务A开始,获得事务ID=100
SELECT * FROM account WHERE id = 1;         -- 读取账户余额1000
                                          -- 此时创建Read View,m_ids=, min_trx_id=100, max_trx_id=102

-- 事务B
BEGIN;                                    -- 事务B开始,获得事务ID=101
UPDATE account SET balance = 800
WHERE id = 1;                               -- 修改余额为800
                                          -- 记录旧值(balance=1000)到Undo Log
                                          -- 更新记录的DB_TRX_ID=101
COMMIT;                                     -- 事务B提交

-- 回到事务A
SELECT * FROM account WHERE id = 1;         -- 在RR隔离级别下,仍然读取到余额1000
                                          -- 因为使用的是事务开始时的Read View
                                          -- 发现记录的DB_TRX_ID=101,在m_ids列表中,不可见
                                          -- 沿着DB_ROLL_PTR找到Undo Log,读取旧版本(balance=1000)

UPDATE account SET balance = balance - 500
WHERE id = 1;                               -- 这是当前读,会读取到最新值800
                                          -- 然后计算800-500=300,更新余额为300
COMMIT;                                     -- 事务A提交
上面的例子展示了在可重复读(RR)隔离级别下,事务A两次读取同一数据的结果雷同(都是1000),就算中心事务B修改了数据。这正是MVCC的魔力!
隔离级别与MVCC的关系

下面详细阐明不同隔离级别下MVCC的表现:

[*]READ UNCOMMITTED:不使用MVCC,直接读取最新值,会出现脏读
[*]READ COMMITTED:使用MVCC,但每次SELECT都创建新的Read View

[*]解决脏读:只能读到已提交事务修改的数据
[*]但存在不可重复读:同一事务内两次读取结果大概不同

[*]REPEATABLE READ:使用MVCC,事务开始时创建Read View并复用

[*]解决脏读和不可重复读:同一事务内多次读取结果雷同
[*]大部门环境下也能解决幻读(通过对新插入记载的DB_TRX_ID判断)

[*]SERIALIZABLE:不使用MVCC,通过加锁实现串行实行,性能最差
面试热门:能把MVCC问倒你的5个题目

面试中常常会问到这些MVCC干系题目,提前准备好:
1. MVCC是如何实现的?

面试官想考察:对底层原理的理解
标准答复:
   MVCC通过在每行记载后面添加三个潜伏字段(DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID)、Undo Log和Read View来实现。当事务修改数据时,旧版本保存在Undo Log中,形成版本链;读取时,通过Read View判断哪个版本对当前事务可见。
2. MVCC能解决幻读吗?

面试官想考察:对隔离级别与并发题目的理解
标准答复:
   MVCC在RR隔离级别下可以解决"快照读"环境下的幻读,因为它只会读取事务开始时就存在的行版本。但无法解决"当前读"环境下的幻读,例如UPDATE…WHERE或FOR UPDATE查询,因为这些操作会读取最新数据。为彻底解决幻读,InnoDB还使用了Next-Key Lock锁算法。
3. 为什么MVCC在RR和RC隔离级别下表现不同?

面试官想考察:对Read View创建时机的理解
标准答复:
   关键区别在于Read View的创建时机。在RC级别下,每次SELECT都会创建新的Read View,所以能看到其他已提交事务的修改。而在RR级别下,只在事务开始时创建一次Read View并复用,所以同一事务内多次读取结果划一,看不到其他事务的修改。
4. MySQL的MVCC和Oracle的MVCC有什么区别?

面试官想考察:对不同数据库实现的了解
标准答复:
   MySQL的MVCC是基于Undo Log的,旧版本数据存储在Undo Log中;Oracle的MVCC是基于回滚段(Rollback Segment)的,采用写转读策略。另外,MySQL默认隔离级别是RR,而Oracle默认是RC。这导致在默认配置下,MySQL解决了不可重复读题目,而Oracle没有。
5. MVCC有什么缺点?

面试官想考察:全面理解和技术取舍意识
标准答复:
   MVCC的主要缺点包罗:1)存储本钱增长,必要额外空间存储多版本数据;2)Undo Log必要定期清算,大概影响性能;3)复杂查询如JOIN在多版本环境下大概会有划一性题目;4)对于写麋集型应用,维护版本链的开销较大。但这些缺点与其带来的并发性能提拔相比,通常是值得的。
进阶扩展:MVCC与锁的关系

MVCC并不能完全替代锁,两者现实是互补关系:


[*]MVCC主要解决读-写辩论:答应读和写并行,进步并发性能
[*]锁主要解决写-写辩论:防止多个事务同时修改同一数据
在RR隔离级别下,InnoDB的做法是:


[*]普通SELECT使用MVCC,不加锁
[*]UPDATE、DELETE等写操作,使用行锁
[*]SELECT … FOR UPDATE或LOCK IN SHARE MODE,也会使用行锁
https://i-blog.csdnimg.cn/direct/5068384ca47a495ea456d704e4827913.png
MVCC性能调优建议

知道了MVCC原理,我们可以针对性地调优数据库:

[*] 合理设置隔离级别:多数应用使用默认的REPEATABLE READ即可,对于读多写少的场景,可以考虑READ COMMITTED
[*] 控制事务大小和连续时间:
-- 不要这样做 ❌
BEGIN;
SELECT ...;
-- 处理大量数据或长时间操作
UPDATE ...;
COMMIT;

-- 应该这样做 ✅
BEGIN;
SELECT ...;
-- 立即处理必要的业务逻辑
UPDATE ...;
COMMIT;

[*] 定期优化表:长时间运行的系统会积累大量Undo Log,影响性能
-- 优化表,清理不必要的历史版本
OPTIMIZE TABLE your_table;

[*] 合理配置Innodb_undo_logs参数:控制回滚段的数目,默认值128适合大多数场景
总结:MVCC就是数据库的"时光机"

MVCC为不同事务提供了各自的数据视图,就像是一个"数据库时光机",让每个事务都能穿越回它开始时的数据状态。
核心知识点:

[*]MVCC如何通过潜伏字段、Undo Log和Read View实现多版本并发控制
[*]不同隔离级别下MVCC的行为差别
[*]MVCC如何解决脏读、不可重复读以及部门幻读题目
[*]MVCC与锁机制如何共同工作
[*]面试中关于MVCC的常见题目及答案
理解MVCC不光能帮你应对面试,更能在现实工作中更好地设计和优化数据库应用。盼望这篇文章能帮助你从根本上搞懂MVCC,下次再碰到干系题目,就能胸有成竹了!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 高频面试,请说说MySQL MVCC 事务隔离机制的实现原理