MySQL是互联网行业的事实标准数据库,而InnoDB作为其最核心的存储引擎,支撑着无数高并发业务场景。它究竟有何魔力,能在保证数据一致性的同时,处理成千上万的并发请求?很多人会脱口而出:因为MVCC。但MVCC具体是如何运作的?本文将深入InnoDB内核,从并发控制的演进说起,一层层揭开这个谜底。
一、并发控制的演进之路
要理解InnoDB的高并发,首先得明白并发控制的基本问题。当多个任务同时操作同一份数据(临界资源)时,如果不加控制,数据一致性就会遭到破坏。因此,必须引入并发控制机制。
技术上,常见的并发控制手段主要有两种:锁(Locking) 和 数据多版本(Multi-Versioning)。
1. 从普通锁到读写锁:并发的第一次飞跃
最朴素的方式是使用普通互斥锁。任何操作(读或写)前都必须加锁,操作完成后解锁。这种方式下,任务完全是串行执行 的,并发度为零。
为了提升读操作的并发,共享锁(S锁)与排他锁(X锁)应运而生。其规则很简单:
-
共享锁之间不互斥 :多个读任务可以同时进行(读读并行)。
-
排他锁与任何锁互斥 :只要有一个写任务,其他所有读和写任务都必须等待(写写、写读互斥 )。
读写锁虽然实现了读读并行,但"写"操作仍然会阻塞所有"读"操作。在高并发写入的场景下,这依然是个巨大的瓶颈。
2. 数据多版本:实现读写并行的关键
能否让"写"也不阻塞"读"呢?答案是肯定的,这就是数据多版本 的核心思想。
其原理非常巧妙:
-
当写任务发生时,它并不直接修改原始数据,而是**创建一份数据副本(新版本)**进行修改。
-
与此同时,其他并发的读任务仍然可以读取旧的、未被修改的数据版本。
-
只有当写任务最终提交 后,新版本的数据才会生效。
通过这种方式,数据多版本成功实现了读写并行,极大提升了并发度。这,就是InnoDB高并发能力的理论基石。
二、InnoDB的实现基石:redo与undo日志
理论需要工程实现来落地。InnoDB通过两种关键的日志来构建其多版本能力。
-
redo日志(重做日志):保证已提交事务的持久性
数据库事务提交后,必须保证数据最终写入磁盘(持久性)。但若每次提交都立即随机写盘,性能将极差。InnoDB的优化是:将修改行为以顺序写 的方式记录到redo日志中,然后再异步地将数据刷回到磁盘。如果数据库崩溃,重启时会通过重放redo日志,恢复所有已提交的事务。redo日志的设计精髓,是用顺序I/O模拟随机I/O,用空间换时间。
-
undo日志(回滚日志)与回滚段:构建数据旧版本的仓库
当事务修改数据时,InnoDB会将修改前的旧版本数据 保存到undo日志中。这些undo日志被集中存储在回滚段(Rollback Segment) 里。
这里用一个例子说明:
假设表
t中有三条数据(1, A), (2, B), (3, C)。一个事务开始后,执行了delete (1, A)和update (3, C) to (3, X)。此时,被删除的(1, A)和被修改前的(3, C)作为旧版本数据,都进入了回滚段。-
如果事务需要回滚,InnoDB就可以利用回滚段中的undo日志,将数据恢复原状。
-
如果事务一直未提交,其他并发事务需要读取这些数据时,就能从回滚段中找到合适的旧版本。
undo日志和回滚段,就是存储数据"过去影像"的仓库,是多版本并发的物理基础。
-
三、MVCC的完整拼图:隐藏字段与快照读
有了undo日志这个"仓库",InnoDB还需要一套机制来精确找到所需的"旧版本"。这就要说到InnoDB为每行数据额外增加的三个隐藏字段:
-
DB_TRX_ID(6字节):记录最近一次修改(更新/删除)该行数据的事务ID。 -
DB_ROLL_PTR(7字节) :回滚指针,指向回滚段中该行旧版本数据的undo日志记录。通过这个指针,可以沿着版本链找到历史数据。 -
DB_ROW_ID(6字节):一个单调递增的行ID。如果表没有显式定义主键,InnoDB会自动使用它来生成聚簇索引。
这三个字段共同构成了MVCC的"寻址系统"。当一个事务执行读取操作时,它并非直接操作基础数据,而是通过一种名为快照读(Snapshot Read) 的机制来获取数据。
-
什么是快照读?
快照读,即一致性不加锁读(Consistent Nonlocking Read) 。在InnoDB中,所有普通的
SELECT语句(不带FOR UPDATE或LOCK IN SHARE MODE)都是快照读。 -
快照读如何工作?
当一个读事务开始时,它会根据当前系统活跃事务等信息,创建一个"视图"(Read View)。之后,在读取每一行数据时,它会根据该行的
DB_TRX_ID和DB_ROLL_PTR,沿着回滚段中的版本链,找到第一个在"视图"创建时就已经提交了的旧版本数据。这个过程完全不需要加锁。 -
为什么快照读是高并发的核心?
因为快照读永远不会被阻塞,也永远不会阻塞其他任何操作。写事务在修改数据时,只是创建新的数据版本并更新版本链;读事务则根据时间点读取对应的旧版本。两者各取所需,互不干扰。正是这种机制,让InnoDB在极高并发下依然能保持流畅的读取性能。
总结与思考
回到文章开头的提问:InnoDB的高并发,究竟是不是因为MVCC?
答案是肯定的,但更精确地说,是因为基于MVCC实现的"快照读"机制。
让我们梳理一下关键点:
-
并发控制的演进 :从串行的普通锁,到读读并行的读写锁,最终发展到读写并行的数据多版本,这是思路上的根本飞跃。
-
工程实现的基石 :redo日志 保证了高性能下的持久性;undo日志与回滚段则作为存储数据旧版本的"仓库",为多版本提供了物理承载。
-
核心机制 :快照读 通过读取回滚段中的历史版本,实现了不加锁的一致性读。这是InnoDB并发能力的核心密码 。在InnoDB中,除了显式加锁的
SELECT ... FOR UPDATE等语句外,所有普通SELECT都是快照读。