MVCC的实现原理

发布于:2025-08-04 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言

为什么在MySQL中,一个事务在读取数据的时候,另一个事务同时修改同一条数据,读事务却不会被阻塞?这背后的功臣就是MVCC

什么是MVCC?

MVCC(多版本并发控制),是一种并发控制方法,主要用于数据库管理系统中,同时为事务提供对数据库的并发访问。MVCC让数据库可以为同一行数据维护多个版本,不同的事务可以看到不同版本的数据,从而实现不加锁的情况下解决读写冲突。

为什么需要MVCC?

传统锁机制的问题

在没有MVCC之前,数据库主要依靠锁来解决并发问题:

这种方式存在明显问题:

  • 性能低下:读写操作互相阻塞
  • 并发度差:大量读操作会严重影响写操作
  • 死锁风险:复杂的锁竞争容易产生死锁

MVCC的优势

MVCC带来的好处:

  • 读写并发:读操作不会阻塞写操作,写操作也不会阻塞读操作
  • 高并发:支持更多的并发事务
  • 无死锁:读操作永远不会死锁
  • 一致性读:保证事务读取数据的一致性

MVCC的实现原理

核心组件

MVCC主要依靠以下几个组件实现:

1. 隐藏字段

InnoDB为每行记录添加了几个隐藏字段:

  • DB_TRX_ID:最后修改该行的事务ID
  • DB_ROLL_PTR:指向undo log中该行的上一个版本
  • DB_ROW_ID:单调递增的行ID(当表没有主键时使用)

2. Undo Log(回滚日志)

Undo Log记录了数据的历史版本,形成一个版本链。

3. Read View(读视图)

Read View是事务开始时的数据库快照,包含:

  • m_ids:当前活跃事务ID列表
  • min_trx_id:活跃事务中最小的事务ID
  • max_trx_id:下一个要分配的事务ID
  • creator_trx_id:创建该Read View的事务ID

版本可见性判断

当事务读取数据时,需要判断某个版本是否对当前事务可见:

// 伪代码:版本可见性判断
public boolean isVisible(long trx_id, ReadView readView) {
    if (trx_id == readView.creator_trx_id) {
        // 是当前事务修改的,可见
        return true;
    }
    
    if (trx_id < readView.min_trx_id) {
        // 在所有活跃事务之前提交的,可见
        return true;
    }
    
    if (trx_id >= readView.max_trx_id) {
        // 在Read View创建之后开启的事务,不可见
        return false;
    }
    
    if (readView.m_ids.contains(trx_id)) {
        // 在活跃事务列表中,不可见
        return false;
    }
    
    // 已提交的事务,可见
    return true;
}

MVCC在不同隔离级别下的表现

READ COMMITTED(读已提交)

在RC隔离级别下,每次SELECT都会创建新的Read View:

特点:

  • 每次读取都创建新的Read View
  • 能读到其他事务已提交的修改
  • 解决了脏读,但存在不可重复读问题

REPEATABLE READ(可重复读)

在RR隔离级别下,同一事务中的所有SELECT使用同一个Read View:

特点:

  • 事务开始时创建Read View,整个事务期间复用
  • 保证了可重复读
  • MySQL默认隔离级别

MVCC的局限性

1. 幻读问题

MVCC无法完全解决幻读问题:

-- 事务A
BEGIN;
SELECT * FROM users WHERE age > 18;  -- 返回5条记录

-- 事务B
INSERT INTO users (name, age) VALUES ('Charlie', 25);
COMMIT;

-- 事务A
SELECT * FROM users WHERE age > 18;  -- 仍然返回5条记录(MVCC保证)

-- 但是...
UPDATE users SET name = CONCAT(name, '_updated') WHERE age > 18;
-- 会更新6条记录!包括事务B插入的记录

2. 写写冲突需要锁

MVCC只解决了读写冲突,写写冲突仍需要锁:

-- 两个事务同时更新同一行
-- 事务A
BEGIN;
UPDATE users SET name = 'Alice' WHERE id = 1;  -- 获得行锁

-- 事务B
BEGIN;
UPDATE users SET name = 'Bob' WHERE id = 1;    -- 被阻塞,等待行锁

网站公告

今日签到

点亮在社区的每一天
去签到