MySQL的MVCC多版本并发控制

MVCC (Multi-Version Concurrency Control) 是 MySQL 实现高并发访问的核心机制,它通过在数据行上维护多个版本,使读写操作可以并发执行而不互相阻塞。

一、MVCC 的作用与价值

主要作用

  1. 读写不阻塞:读操作不会阻塞写操作,写操作也不会阻塞读操作
  2. 提高并发:不同事务可以看到数据的不同版本
  3. 避免脏读:确保事务只能看到已提交的数据
  4. 实现隔离级别:支持 READ COMMITTED 和 REPEATABLE READ 隔离级别

实际价值

  • 读多写少场景下性能大幅提升
  • 避免大量锁等待
  • 实现非锁定一致性读

二、MVCC 核心原理

InnoDB 通过三个隐藏字段和 Undo Log 实现 MVCC:

  1. 隐藏字段

    • DB_TRX_ID:6字节,记录最近修改该行的事务ID
    • DB_ROLL_PTR:7字节,指向该行回滚段的指针(Undo Log)
    • DB_ROW_ID:6字节,隐藏的行ID(如果没有主键)
  2. Undo Log

    • 存储数据被修改前的值
    • 形成版本链,用于事务回滚和其他事务读取
  3. ReadView

    • 事务执行快照读时产生的读视图
    • 包含:
      • m_ids:当前活跃事务ID列表
      • min_trx_id:最小活跃事务ID
      • max_trx_id:预分配的下一个事务ID
      • creator_trx_id:创建该ReadView的事务ID

三、MVCC 工作流程案例

案例背景

有一张账户表:

CREATE TABLE accounts (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  balance DECIMAL(10,2)
);

INSERT INTO accounts VALUES (1, 'Alice', 1000.00);

事务执行时序

时间事务1 (TRX_ID=101)事务2 (TRX_ID=102)事务3 (TRX_ID=103)
T1BEGIN;
T2UPDATE accounts SET balance=900 WHERE id=1;
T3BEGIN;
T4SELECT * FROM accounts WHERE id=1; (看到balance=1000)
T5COMMIT;
T6SELECT * FROM accounts WHERE id=1; (仍看到balance=1000)
T7COMMIT;
T8BEGIN;
T9SELECT * FROM accounts WHERE id=1; (看到balance=900)

版本链形成过程

  1. 初始数据:
       id=1, name='Alice', balance=1000.00, DB_TRX_ID=0, DB_ROLL_PTR=null
  2. 事务1 (TRX_ID=101) 更新:
    • 在Undo Log中记录修改前的值:
           Undo Log Record: balance=1000.00, DB_TRX_ID=0
    • 更新数据行:

           id=1, name='Alice', balance=900.00, DB_TRX_ID=101, DB_ROLL_PTR -> Undo Log
  3. 事务2 (TRX_ID=102) 第一次查询:

    • 创建ReadView:
      • m_ids = [101, 102]
      • min_trx_id = 101
      • max_trx_id = 103
      • creator_trx_id = 102
    • 检查当前行:
      • 行DB_TRX_ID=101 ∈ m_ids 且 ≠ creator_trx_id
      • 通过DB_ROLL_PTR找到Undo Log中的版本
      • 返回balance=1000.00
  4. 事务1提交后,事务2第二次查询:

    • 仍使用之前的ReadView(REPEATABLE READ特性)
    • 即使事务1已提交,仍返回Undo Log中的旧版本
  5. 事务3 (TRX_ID=103) 查询:

    • 创建新的ReadView:
      • m_ids = (事务1已提交,事务2已提交)
      • min_trx_id = 103
      • max_trx_id = 104
      • creator_trx_id = 103
    • 检查当前行:
      • 行DB_TRX_ID=101 < min_trx_id(已提交)
      • 直接返回当前行balance=900.00

四、不同隔离级别下的MVCC

1. READ COMMITTED (提交读)

  • 每次SELECT都生成新的ReadView
  • 能看到其他事务已提交的修改
  • 案例中事务2在T6会看到balance=900

2. REPEATABLE READ (可重复读)

  • 第一次SELECT时生成ReadView,后续复用
  • 整个事务看到相同的数据快照
  • 案例中事务2在T6仍看到balance=1000

五、MVCC与锁的协同

MVCC解决读-写冲突,锁解决写-写冲突:

-- 事务1
BEGIN;
SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 加排他锁
UPDATE accounts SET balance=900 WHERE id=1;
COMMIT;

-- 事务2
BEGIN;
SELECT * FROM accounts WHERE id=1; -- MVCC读,不受锁影响
SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 会被阻塞,直到事务1提交

六、MVCC的局限性

  1. 额外存储:需要维护版本链,占用更多空间
  2. 清理机制:需要定期purge过期版本
  3. 写冲突:无法解决两个事务同时更新同一行的冲突
  4. 长事务问题:长事务会导致版本链过长

七、MVCC优化建议

  1. 控制事务大小:避免长事务
  2. 合理设计索引:减少回表查找
  3. 监控版本链
       SHOW ENGINE INNODB STATUS\G
       -- 查看 HISTORY LIST LENGTH

  4. 定期维护:在低峰期执行OPTIMIZE TABLE
MySQL多版本并发控制MVCC)是一种并发控制机制,它主要是为了解决并发读写冲突的问题。在MVCC机制中,每个事务都可以看到数据库中的一个快照,这个快照是在事务开始时确定的。事务读取数据时,实际上是读取了该快照中的数据,而不是实际的数据。当事务需要修改数据时,MySQL会根据数据的版本号来判断是否可以进行修改。 MVCC的实现原理主要是在每一行数据后面保存多个版本号,并且还需要保存该版本号对应的事务ID。当开始一个事务时,MySQL会为该事务分配一个唯一的事务ID,该事务ID会被用于标记事务对应的数据版本号。当一个事务需要读取数据时,MySQL会根据该事务的事务ID和版本号来判断是否允许读取该数据。如果该事务的事务ID小于等于该数据的版本号,那么就可以读取该数据。如果该事务需要修改数据,则MySQL会为该数据在数据库中创建一个新版本,并将该新版本的版本号和事务ID保存下来。这样,其他事务就可以继续读取原来的版本,而该事务则可以读取新版本并修改数据,从而实现并发控制。 需要注意的是,MVCC只能解决读写冲突的问题,而不能解决写写冲突的问题。此外,MVCC也会占用一定的存储空间,因为每个数据行都需要保存多个版本号和事务ID。因此,在使用MVCC机制时,需要注意存储空间和性能方面的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值