> For the complete documentation index, see [llms.txt](https://bohans.gitbook.io/ji-chu/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bohans.gitbook.io/ji-chu/mysql/ji-chu-zhi-shi/shi-wu/duo-ban-ben-bing-fa-kong-zhi-mvcc.md).

# 多版本并发控制（MVCC）

MySQL 的大多数事务型存储引擎使用的都不是简单的行级锁机制。它们会**将行级锁和可以提高并发性能的多版本并发控制（MVCC）技术结合使用**。

{% hint style="info" %}
可以认为 <mark style="color:blue;">**MVCC**</mark> 是行级锁的一个变种，但是**它在很多情况下避免了加锁操作，因此开销更低**。根据其实现方式，**不仅实现了非阻塞的读操作，写操作也只锁定必要的行**。
{% endhint %}

MVCC 的工作原理是<mark style="color:blue;">**使用数据在某个时间点的快照**</mark>来实现的。这意味着，<mark style="color:blue;">**无论事务运行多长时间，都可以看到数据的一致视图**</mark>，也意味着<mark style="color:blue;">**不同的事务可以在同一时间看到同一张表中的不同数据**</mark>！

<figure><img src="https://1499381545-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMUt9Wh4IwoniQwDIQoI%2Fuploads%2F2KpxNuuUSZ85ybymoaVd%2F44257_43_1.jpg?alt=media&amp;token=4a186fa8-94a1-40c1-9d6d-49b7dddee4d1" alt=""><figcaption></figcaption></figure>

InnoDB 通过<mark style="color:blue;">**为每个事务在启动时分配一个事务 ID**</mark> 来实现 MVCC，该 ID 在事务首次读取任何数据时分配。在该事务中**修改记录**时，**向 Undo 日志写入一条说明如何恢复该修改的 Undo 记录，并且事务的回滚指针指向该 Undo 日志记录**。这就是事务如何在需要时执行回滚的方法。

当不同的会话读取聚簇主键索引记录时，InnoDB 会**将该记录的事务 ID 与该会话的读取视图进行比较**。如果当前状态下的记录**不应可见**（更改它的事务尚未提交），**那么 Undo 日志记录将被跟踪并应用**，直到会话达到一个符合可见条件的事务 ID。这个过程可以一直循环到完全删除这一行的 Undo 记录，然后向读取视图发出这一行不存在的信号。

事务中的记录可以通过在记录的 “info flags” 中设置 “deleted” 位来删除。这在 Undo 日志中也被作为“删除标记”进行跟踪。

值得注意的是，**所有 Undo 日志写入也都会写入 Redo 日志，因为 Undo 日志写入是服务器崩溃恢复过程的一部分，并且是事务性的**。这些 Redo 日志和 Undo 日志的大小也是高并发事务工作机制中的重要影响因素。

<mark style="color:blue;">**在记录中保留这些额外信息带来的结果是，大多数读取查询都不再需要获取锁。它们只是尽可能快地读取数据，确保仅查询符合条件的行即可**</mark>。<mark style="color:blue;">**缺点是存储引擎必须在每一行中存储更多的数据，在检查行时需要做更多的工作，并处理一些额外的内部操作**</mark><mark style="color:blue;">。</mark>

{% hint style="warning" %} <mark style="color:orange;">**MVCC 仅适用于 REPEATABLE READ 和 READ COMMITTED 隔离级别**</mark>。

READ UNCOMMITTED 与 MVCC 不兼容，是因为查询不会读取适合其事务版本的行版本，而是不管怎样都读最新版本。

SERIALIZABLE 与 MVCC 也不兼容，是因为读取会锁定它们返回的每一行。
{% endhint %}
