社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

十万个why:明明 MVCC 已经实现了无锁读,为什么 MySQL 还需要四种隔离级别?

码小辫 • 1 月前 • 112 次点击  

码小辫 百万粉丝认证号

点击关注后,每天可以得到全网有趣的科技内容和一个有趣的程序员灵魂  ▶  ▶  ▶


MVCC 多版本并发控制,读不加锁,读写互不阻塞。

既然读都不用加锁了,数据库的并发问题不就解决了吗?那为什么 MySQL 还要搞出 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE 四种隔离级别?MVCC 一把梭不就完了?


01
MVCC 到底做了什么

InnoDB 的每一行数据都有两个隐藏字段:

  • DB_TRX_ID:最后修改这行数据的事务 ID

  • DB_ROLL_PTR:指向 undo log 中这行的上一个版本

每次 UPDATE 一行数据,旧版本不会被覆盖,而是写到 undo log 里,通过指针串成一条版本链:

当一个事务执行 SELECT 时,InnoDB 不是直接读当前版本,而是生成一个叫 ReadView 的快照,用它来判断版本链上的哪个版本对当前事务可见。

ReadView 里记录了当前所有活跃(还没提交)的事务 ID 列表。判断规则简单粗暴:

  • 这个版本的 trx_id 在我事务开始前就已经提交了 → 可见

  • 这个版本的 trx_id 是我之后才开始的事务 → 不可见,沿着版本链往下找

  • 这个版本的 trx_id 还在活跃列表里(没提交) → 不可见,继续往下找

到这里看起来一切都很完美:读的时候读历史版本,写的时候写当前版本,读写完全不冲突。


02
那为什么还需要隔离级别?

因为我应该看到哪个版本这个问题,不同业务场景的答案不一样。

MVCC 只是提供了一种读历史版本的机制,但具体读哪个历史版本,取决于 ReadView 的创建策略。而这个策略,就是隔离级别在控制的东西。

READ COMMITTED

每次 SELECT 创建新 ReadView

RC 级别下,每次 SELECT 都重新拍一张快照。如果两次 SELECT 之间有其他事务提交了修改,第二次读就能看到新数据,这就是所谓的不可重复读,就是同一个事务里两次读同一行,结果不一样。

REPEATABLE READ

整个事务只创建一次 ReadView

RR 级别下,事务第一次 SELECT 的时候创建 ReadView,后续所有 SELECT 都复用这同一个快照。不管外面的世界怎么变,你在这个事务里看到的数据永远是一致的。

RC 和 RR 用的是完全一样的 MVCC 机制,区别仅仅在于 ReadView 什么时候创建。一个是每次读都重新拍快照,一个是整个事务只拍一次。就这一个时机差异,造成了两种截然不同的隔离效果。


03
READ UNCOMMITTED 为什么不用 MVCC?

READ UNCOMMITTED 的语义是,可以读到其他事务还没提交的修改。

既然连未提交的都能读,那直接读版本链最上面的当前版本就完事了,根本不需要创建 ReadView 去过滤。MVCC 的意义在于屏蔽不该看到的版本,如果你什么都想看,那 MVCC 就是多此一举。

所以 READ UNCOMMITTED 不是"不能用 MVCC",而是用了也没意义。


04
SERIALIZABLE 为什么也不用 MVCC?

SERIALIZABLE 走的是另一个极端,所有读操作都加共享锁。

-- 在 SERIALIZABLE 级别下,这条普通 SELECT
SELECT*FROMuserWHERE id =1;
-- 会被自动转换成
SELECT*FROMuserWHERE id =1 LOCK IN SHARE MODE;

读也加锁意味着读写之间会互相阻塞,跟 MVCC 的设计目标(读写不阻塞)完全背道而驰。SERIALIZABLE 放弃了 MVCC 的并发优势,换来的是最强的一致性保证,事务之间完全串行执行。


05
一张图看清关系


06
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 或者单靠锁,而是两者的分工配合。


07
说在最后

MVCC 是一个底层的并发控制工具,解决的是读操作如何不加锁地获取一致数据这个问题。

但一致到什么程度:是只看已提交的最新数据(RC),还是整个事务期间数据不变(RR),还是连未提交的都能看(RU),还是读也加锁保证绝对串行(Serializable),这是业务层面的选择,由隔离级别来控制。

MVCC 提供能力,隔离级别定义策略,它们不是同一层面的东西

就这点事而已!


高质量原创回顾


中国最难入职的IT公司...


千辛万苦终于上岸考上公务员了,实现了月薪从25000降到5000的飞跃


外包同事参加聚餐,刚到包间门口,就听主管说:都自己人,以后有啥脏活累活都给外包,他们本就是服务咱的,不要不好意思


现在的失业率很高,招一个技术员,收到了18份简历。 其实你们找不到工作,并不是因为你们不够优秀,而是企业失去了培养的信心。


听说互联网不卡35岁了?


程序员为保饭碗,开始“防御性编程”.....


字节这波操作,直接改变了大家的习惯?


华为员工爆料:OD大部分人是入职即巅峰!D4级,除了升D5时月薪涨了3k,其他时候从来没涨过


别忘了点分享、收藏、在看、点赞哦!


图片

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/201873