来源丨经授权转自 程序员小富(ID:chengxy-nds)
MVCC 多版本并发控制,读不加锁,读写互不阻塞。
既然读都不用加锁了,数据库的并发问题不就解决了吗?那为什么 MySQL 还要搞出 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、
SERIALIZABLE 四种隔离级别?MVCC 一把梭不就完了?
InnoDB 的每一行数据都有两个隐藏字段:
DB_ROLL_PTR:指向 undo log 中这行的上一个版本
每次 UPDATE 一行数据,旧版本不会被覆盖,而是写到 undo log 里,通过指针串成一条版本链:
当一个事务执行 SELECT 时,InnoDB 不是直接读当前版本,而是生成一个叫 ReadView 的快照,用它来判断版本链上的哪个版本对当前事务可见。
ReadView 里记录了当前所有活跃(还没提交)的事务 ID 列表。判断规则简单粗暴:
这个版本的 trx_id 在我事务开始前就已经提交了 → 可见
这个版本的 trx_id 是我之后才开始的事务 → 不可见,沿着版本链往下找
这个版本的 trx_id 还在活跃列表里(没提交) →
不可见,继续往下找
到这里看起来一切都很完美:读的时候读历史版本,写的时候写当前版本,读写完全不冲突。
因为我应该看到哪个版本这个问题,不同业务场景的答案不一样。
MVCC 只是提供了一种读历史版本的机制,但具体读哪个历史版本,取决于 ReadView 的创建策略。而这个策略,就是隔离级别在控制的东西。
每次 SELECT 创建新 ReadView
RC 级别下,每次 SELECT 都重新拍一张快照。如果两次 SELECT 之间有其他事务提交了修改,第二次读就能看到新数据,这就是所谓的不可重复读,就是同一个事务里两次读同一行,结果不一样。
整个事务只创建一次 ReadView
RR 级别下,事务第一次 SELECT 的时候创建 ReadView,后续所有 SELECT 都复用这同一个快照。不管外面的世界怎么变,你在这个事务里看到的数据永远是一致的。
RC 和 RR 用的是完全一样的 MVCC 机制,区别仅仅在于 ReadView 什么时候创建。一个是每次读都重新拍快照,一个是整个事务只拍一次。就这一个时机差异,造成了两种截然不同的隔离效果。
READ UNCOMMITTED 为什么不用 MVCC?READ UNCOMMITTED 的语义是,可以读到其他事务还没提交的修改。
既然连未提交的都能读,那直接读版本链最上面的当前版本就完事了,根本不需要创建 ReadView 去过滤。MVCC 的意义在于屏蔽不该看到的版本,如果你什么都想看,那 MVCC 就是多此一举。
所以 READ UNCOMMITTED 不是"不能用 MVCC",而是用了也没意义。
SERIALIZABLE 为什么也不用 MVCC?SERIALIZABLE 走的是另一个极端,所有读操作都加共享锁。
-- 在 SERIALIZABLE 级别下,这条普通 SELECT
SELECT*FROMuserWHERE id =
1;
-- 会被自动转换成
SELECT*FROMuserWHERE id =1 LOCK IN SHARE MODE;
读也加锁意味着读写之间会互相阻塞,跟 MVCC 的设计目标(读写不阻塞)完全背道而驰。SERIALIZABLE 放弃了 MVCC 的并发优势,换来的是最强的一致性保证,事务之间完全串行执行。
MVCC 只解决了读的问题(快照读),写的时候依然需要锁。
InnoDB 里有两种读:
快照读(Snapshot Read):普通 SELECT,走 MVCC,不加锁。
当前读(Current Read):SELECT ... FOR UPDATE、UPDATE、DELETE、
INSERT。这些必须读到当前最新版本(不然 UPDATE 可能基于过时数据修改),所以不走 MVCC,走行锁。
-- 快照读:走 MVCC,读的可能是历史版本,不加锁
SELECT*FROM account WHERE id =1;
-- 当前读:加排他锁,读最新版本
SELECT*
FROM account WHERE id =1FORUPDATE;
-- 当前读:UPDATE 内部会先读取当前最新值
UPDATE account SET balance = balance -100WHERE id =1;
InnoDB 在 RC 和 RR 级别下的实际策略是:
读用 MVCC → 无锁快照读,不阻塞写
写用行锁 → 互斥,保证数据安全
读写之间不冲突(读的是历史版本,写的是当前版本),写写之间通过行锁互斥。这才是 InnoDB 并发性能好的真正原因,不是单靠 MVCC 或者单靠锁,而是两者的分工配合。
MVCC 是一个底层的并发控制工具,解决的是读操作如何不加锁地获取一致数据这个问题。
但一致到什么程度:是只看已提交的最新数据(RC),还是整个事务期间数据不变(RR),还是连未提交的都能看(RU),还是读也加锁保证绝对串行(Serializable),这是业务层面的选择,由隔离级别来控制。
MVCC 提供能力,隔离级别定义策略,它们不是同一层面的东西
就这点事而已!