MySQL基础-MVCC


MVCC存储在哪里,作用是什么

MVCC是什么

  • 传统的数据库并发控制通常使用锁机制,在并发量高的情况下容易导致阻塞和性能下降。
  • 读不阻塞写,写不阻塞读;为每个事务维护一个数据快照;创建新版本而不是覆盖。

MVCC的实现原理

好的,我们来详细解释一下 MVCC(多版本并发控制)的实现机制。

MVCC 的核心思想是维护数据的多个版本,使得读操作可以不阻塞写操作,写操作也不会阻塞读操作。它主要依赖于以下几个关键组成部分和机制:

  1. 事务 ID (Transaction ID)

    每个事务在开始时都会被分配一个唯一的、递增的事务 ID。这个 ID 在 MVCC 中用于标记数据是由哪个事务创建或修改的。

  2. 数据行的版本链 (Version Chain)

    MVCC 数据库中的每一行数据通常都会有几个隐藏的列,用于记录该行的版本信息。这些隐藏列通常包括:

    • 创建事务 ID (TrxId 或 DB_TRX_ID): 记录创建该行数据的事务 ID。
    • 删除事务 ID (DelTrxId) 或 指向 Undo Log 的指针 (DB_ROLL_PTR):
      • 有些实现会记录删除该行数据的事务 ID。
      • 更常见的实现(如 InnoDB)是使用一个回滚指针 (DB_ROLL_PTR) 指向 Undo Log 中记录了该行修改前数据的一条记录。通过这个指针,可以将同一逻辑行(同一条记录)的不同版本通过 Undo Log 连接起来,形成一个版本链。最新的版本位于链的头部,旧版本通过指针连接到 Undo Log 中。
  3. Undo Log (回滚日志)

    Undo Log 在 MVCC 中扮演着至关重要的角色:

    • 存储旧版本数据: 当一个事务修改(UPDATE 或 DELETE)一行数据时,修改前的旧版本数据会被写入 Undo Log。
    • 构建版本链: 数据行中的回滚指针 (DB_ROLL_PTR) 正是指向 Undo Log 中的对应记录,从而构建起数据行的版本链。这个链条记录了该行数据从最初创建到当前版本的所有修改历史。
    • 支持回滚: 当事务需要回滚时,可以通过 Undo Log 中的记录将数据恢复到修改前的状态。
    • 支持快照读: 这是 MVCC 实现非阻塞读的关键。当一个事务需要读取数据时,它会根据自身的事务 ID 和可见性规则,沿着版本链在 Undo Log 中找到符合条件的、对当前事务可见的数据版本进行读取。
  4. Read View (读视图)

    每个事务在启动时(或者根据隔离级别的不同,在执行特定语句时)会创建一个 Read View(或类似的机制,如快照时间戳)。Read View 记录了当前活跃(尚未提交)的事务 ID 列表。这个 Read View 是判断数据版本对当前事务是否可见的关键。

    Read View 通常包含以下信息:

    • m_ids: 当前活跃的事务 ID 列表。
    • min_trx_id: m_ids 列表中最小的事务 ID。
    • max_trx_id: 在创建 Read View 时,系统中应该分配给下一个事务的 ID,即当前最大的事务 ID + 1。
    • creator_trx_id: 创建这个 Read View 的事务的 ID。
  5. 可见性判断规则

    当一个事务尝试读取一行数据时,数据库会根据该事务的 Read View 和数据行版本(由创建事务 ID DB_TRX_ID 标记)来判断这个版本是否对当前事务可见。具体的判断规则会根据事务隔离级别(如 Read Committed 或 Repeatable Read)有所不同,但基本原则如下:

    • 如果数据行版本的创建事务 ID (DB_TRX_ID) 小于 Read View 中的 min_trx_id,表示这个版本是由一个在当前事务启动之前就已经提交的事务所创建,因此该版本对当前事务可见。
    • 如果数据行版本的创建事务 ID (DB_TRX_ID) 大于等于 Read View 中的 max_trx_id,表示这个版本是由一个在当前事务启动之后才开始的事务所创建,因此该版本对当前事务不可见。
    • 如果数据行版本的创建事务 ID (DB_TRX_ID) 在 Read View 的 min_trx_id 和 max_trx_id 之间:
      • 如果 DB_TRX_ID 等于 Read View 中的 creator_trx_id,表示这个版本是当前事务自己修改的,因此对当前事务可见。
      • 如果 DB_TRX_ID 在 Read View 的 m_ids 列表 中,表示这个版本是由一个在当前事务启动时仍然活跃(但尚未提交)的事务所创建,因此对当前事务不可见。
      • 如果 DB_TRX_ID 不在 Read View 的 m_ids 列表 中,表示这个版本是由一个在当前事务启动时已经提交的事务所创建(但其 ID 在 min_trx_id 和 max_trx_id 之间),因此对当前事务可见。

    如果当前数据行版本对当前事务不可见,数据库会沿着 Undo Log 中的版本链向前查找更旧的版本,并对找到的每个版本重复上述可见性判断,直到找到一个可见的版本或者版本链的末端。

读写操作在 MVCC 中的处理:

  • 读操作: 当一个事务执行读操作(例如 SELECT)时,它会使用自己的 Read View,根据上述可见性判断规则,从数据行的版本链中找到对自身可见的最新版本进行读取。这个过程不涉及对数据的加锁,因此读操作不会阻塞写操作。
  • 写操作 (UPDATE/DELETE): 当一个事务执行写操作时,它首先会像读操作一样找到对自身可见的最新版本数据。然后,它会将该版本的旧数据写入 Undo Log,并在数据行中创建或修改隐藏列,记录自己的事务 ID 和指向 Undo Log 的指针。新的数据版本只对创建它的事务或者后续启动并满足可见性规则的事务可见。写操作通常仍然需要对要修改的数据行加锁(写锁),以防止其他事务同时修改同一行,但这个锁通常只持续到事务提交或回滚,且不会阻塞读操作。
  • 写操作 (INSERT): 当一个事务插入一行新数据时,它会在数据行中记录自己的事务 ID 作为创建事务 ID。新插入的数据行对创建它的事务可见,对于其他事务,其可见性也遵循上述可见性判断规则。

过期版本的清理 (Purge)

随着事务不断进行修改,数据行会产生越来越多的旧版本存储在 Undo Log 中。这些旧版本如果不再被任何活跃事务所需要,就需要被清理掉,以释放存储空间。这个清理过程通常由一个后台线程(Purge 线程)负责。Purge 线程会根据当前系统中所有活跃事务的 Read View,确定哪些 Undo Log 中的旧版本数据已经不再可能被任何事务读取,然后进行物理删除。长事务会持有旧版本的 Read View,从而阻止对旧版本数据的清理,可能导致 Undo Log 不断增长。

总结:

MVCC 通过为每个事务提供一个数据快照,并利用事务 ID、数据行版本链和 Undo Log 来存储和管理数据的多个版本。通过 Read View 和可见性判断规则,MVCC 使得读操作可以并发进行而不会被写操作阻塞,显著提高了数据库的并发性能和吞吐量,同时提供了良好的事务隔离性(通常是 Snapshot Isolation 或 Read Committed)。

MVCC存储在哪里

  • 数据行中的隐藏列: InnoDB 为每行数据添加了几个隐藏的系统列,其中与 MVCC 密切相关的主要有两个:

    • DB_TRX_ID:记录了最近一次修改(插入或更新)该行的事务 ID。删除在内部也被视为一种更新,会设置一个特殊的位标记。
    • DB_ROLL_PTR:这是一个回滚指针,指向 Undo Log 中的一条记录。如果该行是被更新的,Undo Log 记录包含了重建更新前行内容所需的信息。通过这个指针和 Undo Log,可以找到该行的上一个版本。
  • Undo Log(回滚日志):

    • 作用: Undo Log 是存储旧版本数据的地方。每当事务修改数据时,旧版本的数据会被写入 Undo Log。
    • 存储位置: Undo Log 存储在称为回滚段(rollback segment)的数据结构中,这些回滚段位于 Undo 表空间。Undo 表空间可以是共享的(在 ibdataX 文件中)或者独立的文件。
    • MVCC 的依赖: 当一个事务需要读取某个数据行的旧版本时(根据事务自身的快照时间戳和数据行中的 DB_TRX_ID 判断),它会利用 DB_ROLL_PTR 沿着 Undo Log 的链条找到并重构出符合其可见性条件的旧版本数据。

评论