2025-01-15🌱上海: ☀️ 🌡️+4°C 🌬️↓19km/h
# MySQL 是如何实现事务的?
# 简要回答
主要是通过锁、Redo log、Undo Log 、MVCC 来实现事务
通过事务的特性来分析
-
原子性: undo log(回滚日志),它会记录事务的反向操作,也就是保存数据的历史版本,用于事务的回滚,可以在事务执行失败之后恢复之前的数据,实现原子性和隔离性
-
一致性:通过原子性、隔离性、持久性来达到一致性的目的
-
隔离性:
-
Mysql 利用锁机制,通过对数据并发修改的控制,满足事务的隔离性。
-
MVCC(多版本并发控制),满足了非锁定读的需求,提高了并发度,实现了读已提交和可重复读两种隔离级别,实现了事务的隔离性
-
持久性: redo log 通过记录事务对数据库的所有修改,在崩溃时恢复未提交的更改,用来满足事务的持久性
# 补充回答
# binlog、redo log 和 undo log 的作用及区别?
三者都是日志类型文件,但是各自的作用和实现方法有所不同
- binlog 主要用来对数据库进行数据备份、崩溃恢复和数据复制等操作,redo log、和 undo log 主要用于事务管理,记录的都是数据修改操作和回滚操作。redolog 用来做恢复,undolog 用来做回滚。
- binlog 是 MySQL 用于记录数据库中的所有 DDL、DML 语句的一种二进制日志,它记录了所有对数据库结构和数据的修改操作。binlog 主要用来对数据库进行数据备份、灾难恢复和数据复制等操作。binlog 的格式分为基于语句的格式和基于行的格式。
- redolog 是 MySQL 用于实现崩溃恢复和数据持久性的一种机制
- 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 中会有一条记录的多个快照,那么在一时刻发生普通读操作时要读哪个快照呢?
接下来就需要了解另外几个概念信息了。
# 行记录的隐式字段
首先了解一下行记录存储格式,
从上面的图可以看到数据库中的每行记录中,除了保存了我们自己定义的一些字段外,还有一些重要隐式字段:
- db_row_id:隐藏主键,如果我们没有给这个表创建主键,那么会以这个字段来创建聚簇索引。
- db_trx_id:对这条记录做了最新一次修改的事务的 ID
- db_roll_ptr:回滚指针,指向这条记录的上一个版本,其实他指向的就是 Undo Log 中的上一个版本的快照的地址。
以上字段,只有聚簇索引的行记录才会有,普通二级索引中是没有这些值的。二级索引的 MVCC 支持,后面再了解
每次记录变更之前都会先存储一份快照到 undo log 中,那么这几个隐式字段也会跟着记录一起保存在 undo log,同时这个几个字段也是构成版本链的重要成分。db_trx_id、db_roll_ptr 是重点。快照链表如下:
# 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 进行可见性比较,如果找到一条快照,则返回,找不到则返回空。
# MySQL 中的日志类型有哪些?以及它们的作用和区别是什么?
# 简要回答
三者都是日志类型文件,但是各自的作用和实现方法有所不同
- binlog 主要用来对数据库进行数据备份、崩溃恢复和数据复制等操作,redo log、和 undo log 主要用于事务管理,记录的都是数据修改操作和回滚操作。redolog 用来做恢复,undolog 用来做回滚。
- binlog 是 MySQL 用于记录数据库中的所有 DDL、DML 语句的一种二进制日志,它记录了所有对数据库结构和数据的修改操作。binlog 主要用来对数据库进行数据备份、灾难恢复和数据复制等操作。binlog 的格式分为基于语句的格式和基于行的格式。
- redolog 是 MySQL 用于实现崩溃恢复和数据持久性的一种机制
- 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 |