2025-01-15🌱上海: ☀️ 🌡️+4°C 🌬️↓19km/h

# MySQL 是如何实现事务的?

# 简要回答

主要是通过锁、Redo log、Undo Log 、MVCC 来实现事务

通过事务的特性来分析

  1. 原子性: undo log(回滚日志),它会记录事务的反向操作,也就是保存数据的历史版本,用于事务的回滚,可以在事务执行失败之后恢复之前的数据,实现原子性和隔离性

  2. 一致性:通过原子性、隔离性、持久性来达到一致性的目的

  3. 隔离性

  4. Mysql 利用锁机制,通过对数据并发修改的控制,满足事务的隔离性。

  5. MVCC(多版本并发控制),满足了非锁定读的需求,提高了并发度,实现了读已提交和可重复读两种隔离级别,实现了事务的隔离性

  6. 持久性: redo log 通过记录事务对数据库的所有修改,在崩溃时恢复未提交的更改,用来满足事务的持久性

# 补充回答

# binlog、redo log 和 undo log 的作用及区别?

三者都是日志类型文件,但是各自的作用和实现方法有所不同

  1. binlog 主要用来对数据库进行数据备份、崩溃恢复和数据复制等操作,redo log、和 undo log 主要用于事务管理,记录的都是数据修改操作和回滚操作。redolog 用来做恢复,undolog 用来做回滚。
  2. binlog 是 MySQL 用于记录数据库中的所有 DDL、DML 语句的一种二进制日志,它记录了所有对数据库结构和数据的修改操作。binlog 主要用来对数据库进行数据备份、灾难恢复和数据复制等操作。binlog 的格式分为基于语句的格式和基于行的格式。
  3. redolog 是 MySQL 用于实现崩溃恢复和数据持久性的一种机制
  4. undolog 则用于事务回滚或者系统崩溃时撤销(回滚)事务所做的修改,同时 Undo log 还支持 MVCC(多版本并发控制)机制,用于在并发事务执行时提供一定的隔离性。
对比维度 Binlog Redolog Undolog
定义与用途 记录数据库变更信息,用于数据库恢复和复制,如主从复制场景及数据误操作后的恢复 保证事务的持久性,事务提交时,若数据未持久化到磁盘,系统崩溃后可通过其恢复数据 用于记录事务执行过程中数据修改前的值,支持事务回滚操作、MVCC
记录内容 数据库层面操作,如基于语句模式下的 SQL 语句或基于行模式下的行数据变化情况 物理层面数据修改,对数据页的修改信息,包括修改字节偏移量、修改后的数据内容等 数据修改前的旧值,以便回滚时恢复数据
写入时机与持久化策略 事务提交后写入,可配置不同持久化策略,如每次提交后立即同步磁盘或累积一定数量事务后同步 事务执行过程中不断写入,事务提交前必须先持久化到磁盘(先写入 redo log buffer,再刷到磁盘 redo log 文件) 事务执行过程中写入,事务开始分配空间记录旧值,事务提交后可清理,回滚时使用记录恢复数据
文件格式与存储位置 二进制文件,由 MySQL 定义格式,默认存于数据库数据目录下,文件名如 “binlog.xxxxxx” 由 InnoDB 存储引擎管理,多个 redo log 文件循环使用,存于 InnoDB 数据目录下,文件大小和数量可配置 存储在 InnoDB 共享表空间或独立 undo 表空间(依配置而定),与数据页管理紧密结合
支持引擎 innodb、myiasm、... innodb innodb

# MySQL 中的 MVCC 是什么?

# 简要回答

MVCC,多版本并发控制。和数据库锁一样,也是一种并发控制的解决方案。允许多个事务同时读取和写入数据库,而无需互相等待,从而提高数据库的并发性能。

在 MVCC 中,数据库会为每个事务创建一个数据快照。每当数据发生修改时,MySQL 不会覆盖原本的数据,而是生成一个存储版本号和时间戳的记录,多版本之间串联起来形成一条版本连,这样不同时刻启动的事务可以无锁的获得不同版本的数据。此时读写操作不会阻塞。

写操作会创建信息的数据版本,但只有在事务提交后,新版本才会对其他事务可见。未提交的事务的修改不会影响其事务的读取,历史版本记录可供已经启动的事务读取。

# 补充回答

在数据库中,对于数据的操作主要有两种,分别是读和写。在并发场景下,就有可能出现以下三种情况:

  • 读 - 读并发
  • 读 - 写并发
  • 写 - 写并发

读 - 读并发是不会出现问题的,写 - 写并发这种情况比较常用的就是通过加锁的方式实现。读 - 写并发则可以通过 MVCC 机制解决。

# 扩展回答

对于 MVCC,我认为了解前需要了解以下几个概念:

  • 快照读和当前读
  • Undo Log
  • 行记录的隐藏字段
  • Read View(视图)

# 快照读和当前读

所谓快照读,就是读取的是快照数据,也就是快照生成的那一刻数据,普通读(无锁读)就是快照读。如:

SELECT * FROM TABLE WHERE ...;

和快照读相对应的就是当前读,当前读就是读取最新数据。加锁读、增删改都会进行当前读,比如:

SELECT * FROM TABLE LOCK IN SHARE MODE;
SELECT * FROM TABLE FOR UPDATE;
INSERT INTO TABLE ...
DELETE FROM TABLE ...
UPDATE TABLE ...

快照读是 MVCC 实现的基础,当前读是悲观锁实现的基础

那么快照是存在哪里的呢?

这里就需要了解下一个概念 Undo Log

# Undo Log

undo log 是事务日志之一,同时它是一种用于回退的日志,在事务没有提交之前,MySQL 会记录更新前的数据到 undo log 日志文件中,当事务回滚时或者数据库崩溃时,可以利用 undo log 进行回退。

上面提到的 "更新前的数据" 就是我们前面提到的快照。

一个记录在同一时刻会有多个事务在执行(也就是读 - 写并发),undo log 中会有一条记录的多个快照,那么在一时刻发生普通读操作时要读哪个快照呢?

接下来就需要了解另外几个概念信息了。

# 行记录的隐式字段

首先了解一下行记录存储格式,
image.png

从上面的图可以看到数据库中的每行记录中,除了保存了我们自己定义的一些字段外,还有一些重要隐式字段:

  • db_row_id:隐藏主键,如果我们没有给这个表创建主键,那么会以这个字段来创建聚簇索引。
  • db_trx_id:对这条记录做了最新一次修改的事务的 ID
  • db_roll_ptr:回滚指针,指向这条记录的上一个版本,其实他指向的就是 Undo Log 中的上一个版本的快照的地址。

以上字段,只有聚簇索引的行记录才会有,普通二级索引中是没有这些值的。二级索引的 MVCC 支持,后面再了解

每次记录变更之前都会先存储一份快照到 undo log 中,那么这几个隐式字段也会跟着记录一起保存在 undo log,同时这个几个字段也是构成版本链的重要成分。db_trx_id、db_roll_ptr 是重点。快照链表如下:
image.png

# Read View

Read View 主要来帮我们解决可见性的问题的,即他会来告诉我们本次事务应该看到哪个快照,不应该看到哪个快照

在 MySQL 中,只有 READ COMMITTED 和 REPEATABLE READ 这两种事务隔离级别才会使用快照读。

  • 在 RR 中,快照会在事务开始时生成,只有在本事务中对数据进行更改才会更新快照。
  • 在 RC 中,每次读取都会重新生成一个快照,总是读取行的最新版本。

在 Read View 中有几个重要的属性:

  • trx_ids,表示在生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
  • low_limit_id,应该分配给下一个事务的 id 值。
  • up_limit_id,未提交的事务中最小的事务 ID。
  • creator_trx_id,创建这个 Read View 的事务 ID。

每开启一个事务,我们都会从数据库中获得一个事务 ID,这个事务 ID 是自增长的,通过 ID 大小,我们就可以判断事务的时间顺序。
假设当前事务要读取某一个记录行,该记录行的 db_trx_id(即最新修改该行的事务 ID)为 trx_id,那么,就有以下几种情况了:

  • 1、trx_id< up_limit_id,即小于 5 的事务,说明这些事务在生成 ReadView 之前就已经提交了,那么该事务的结果就是可见的。
  • 2、trx_id>low_limit_id,即大于 8 的事务,说明该事务在生成 ReadView 后才生成,所以该事务的结果就是不可见的。
  • 3、up_limit_id<trx_id<low_limit_id,即大于等于 5,小于 8,这种情况下,会再拿事务 ID 和 Read View 中的 trx_ids 进行逐一比较。
    • 如果,事务 ID 在 trx_ids 列表中,那么表示在当前事务开启时,这个事务还是活跃的,那么这个记录对于当 前事务来说应该是不可见的。
    • 如果,事务 id 不在 trx_ids 列表中,那么表示的是在当前事务开启之前,其他事务对数据进行修改并提交 了,所以,这条记录对当前事务就应该是可见的。
    • 当然这里有个例外情况,那就是这个 trx_id=creator_trx_id,那么就肯定是可见的

所以,当读取一条记录的时候,经过以上判断,发现记录对当前事务可见,那么就直接返回就行了。那么如果不可见怎么办?没错,那就需要用到 undo log 了。

当数据的事务 ID 不符合 Read View 规则时候,那就需要从 undo log 里面获取数据的历史快照,然后数据快照的事务 ID 再来和 Read View 进行可见性比较,如果找到一条快照,则返回,找不到则返回空。
image.png

# MySQL 中的日志类型有哪些?以及它们的作用和区别是什么?

# 简要回答

三者都是日志类型文件,但是各自的作用和实现方法有所不同

  1. binlog 主要用来对数据库进行数据备份、崩溃恢复和数据复制等操作,redo log、和 undo log 主要用于事务管理,记录的都是数据修改操作和回滚操作。redolog 用来做恢复,undolog 用来做回滚。
  2. binlog 是 MySQL 用于记录数据库中的所有 DDL、DML 语句的一种二进制日志,它记录了所有对数据库结构和数据的修改操作。binlog 主要用来对数据库进行数据备份、灾难恢复和数据复制等操作。binlog 的格式分为基于语句的格式和基于行的格式。
  3. redolog 是 MySQL 用于实现崩溃恢复和数据持久性的一种机制
  4. undolog 则用于事务回滚或者系统崩溃时撤销(回滚)事务所做的修改,同时 Undo log 还支持 MVCC(多版本并发控制)机制,用于在并发事务执行时提供一定的隔离性。
对比维度 Binlog Redolog Undolog
定义与用途 记录数据库变更信息,用于数据库恢复和复制,如主从复制场景及数据误操作后的恢复 保证事务的持久性,事务提交时,若数据未持久化到磁盘,系统崩溃后可通过其恢复数据 用于记录事务执行过程中数据修改前的值,支持事务回滚操作、MVCC
记录内容 数据库层面操作,如基于语句模式下的 SQL 语句或基于行模式下的行数据变化情况 物理层面数据修改,对数据页的修改信息,包括修改字节偏移量、修改后的数据内容等 数据修改前的旧值,以便回滚时恢复数据
写入时机与持久化策略 事务提交后写入,可配置不同持久化策略,如每次提交后立即同步磁盘或累积一定数量事务后同步 事务执行过程中不断写入,事务提交前必须先持久化到磁盘(先写入 redo log buffer,再刷到磁盘 redo log 文件) 事务执行过程中写入,事务开始分配空间记录旧值,事务提交后可清理,回滚时使用记录恢复数据
文件格式与存储位置 二进制文件,由 MySQL 定义格式,默认存于数据库数据目录下,文件名如 “binlog.xxxxxx” 由 InnoDB 存储引擎管理,多个 redo log 文件循环使用,存于 InnoDB 数据目录下,文件大小和数量可配置 存储在 InnoDB 共享表空间或独立 undo 表空间(依配置而定),与数据页管理紧密结合
支持引擎 innodb、myiasm、... innodb innodb