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

# MySQL 中的事务隔离级别有哪些?

# 事务的隔离级别

  • 读未提交(READ UNCOMMITTED) 是最低的隔离级别,在这种隔离级别下,一个事务可以读到另一个事务未提交的数据。这种隔离级别下会存在幻读不可重复读脏读的问题。
  • 读已提交 (READ COMMITTED) 在一个事务修改数据过程中,如果事务还没提交,其它事务不能读该数据。所以,这种隔离级别是可以避免脏读的发生的,但是可能会引发不可重复读问题,即在同一个事务中,相同的查询可能返回不同的结果。
  • 可重复读 (REPEATABLE READ) 在这个级别下,确保在一个事务中的多个查询返回的结果是一致的。这可以避免不可重复读问题,但可能会引发幻读问题,即在同一个事务中多次查询可能返回不同数量的行(MySQL 默认的隔离级别
  • ** 串行化 (SERIALIZABLE) 最高的隔离级别,** 在这个级别下,事务串行执行,每个事务都会等待前一个事务执行完毕才会开始执行。虽然可以避免所有的并发问题,但是会大大降低并发性能。

# 不同的隔离级别下可能存在的读取异常问题

这张表格展示了不同数据库隔离级别下,可能出现的三种数据读取问题(脏读、不可重复读、幻读)的情况。

隔离级别 脏读 (DR, Dirty Read) 不可重复读 (NR, NonRepeatable Read) 幻读 (PR, Phantom Read)
能读到未提交的数据,RU, READ-UNCOMMITTED y y y
能读到已提交的数据,RC, READ-COMMITTED N y y
可重复读 RR,REPEATABLE-READ N N y
串行执行 SERIALIZABLE N N N

# 扩展知识

# 事务隔离级别相关命令

  1. 查看当前会话隔离级别

select @@tx_isolation; ![image.png](https://cdn.easymuzi.cn/img/20250115135303138.png) 在 MySQL 8.0 中:SELECT @@transaction_isolation;`

  1. ** 查看系统当前隔离级别
    select @@global.tx_isolation;
    image.png
  2. 设置当前会话隔离级别
    set session transaction isolatin level repeatable read;
  3. 设置系统当前隔离级别
    set global transaction isolation level repeatable read;
  4. 命令行,开始事务时
    set autocommit=off 或者 start transaction

# MySQL 默认的事务隔离级别是什么?为什么选择这个级别?

# 简要回答

MySQL 的默认隔离级别是 RR,就是为了兼容历史上 statement 格式的 binlog。

# 扩展知识

为了进一步分析 binlog statement 格式以及可重复级别的影响,需要先从主从复制开始

# 主从复制

MySQL 的定位就是提供一个稳定的关系型数据库。而为了要解决单点故障带来的问题,需要采用主从复制的机制。

所谓主从复制,其实就是通过搭建 MySQL 集群,整体对外提供服务,集群中的机器分为主服务器(Master)和从服务器(Slave),主服务器提供写服务,从服务器提供读服务。

为了保证主从服务器之间的数据一致性,就需要进行数据同步,数据同步的过程就需要通过 binlog 进行的。主备机制如下
image.png
主从库的数据同步流程如下:
image.png
MySQL 在主从复制的过程中,数据的同步是通过 bin log 进行的,简单理解就是主服务器把数据变更记录到 bin log 中,然后再把 bin log 同步传输给从服务器,从服务器接收到 bin log 之后,再把其中的数据恢复到自己的数据库存储中。

# MySQL 的 binlog 有几中格式?

# 1.1.1. statement

格式为 statement 时,binlog 里面记录的就是 SQL 语句的原文,也就是在数据库中执行的 SQL 会原封不懂的记录在 binlog 中。

这种格式因为会导致主从同步的数据不一致问题,目前使用较少。

# 1.1.2. row

格式为 row 时,binlog 会记录每个数据具体行的更新细节。这意味着二进制日志中的每个条目都会详细列出发生变更的行的内容和修改。好处就是不会导致主从不一致,缺点就是记录的内容更多。因为记录的内容更多,在数据恢复的时候,会需要更长的时间,也会导致磁盘 IO 和网络 IO 都比较高

# 1.1.3. mixed

简单来说,就是混合模式,把 statement 和 row 结合了,MySQL 会根据 SQL 的情况,自动在 row 和 statement 中互相切换选择一个认为合适的格式进行记录。

但是在 RR 下,row 和 statement 都可以生效,在 RC 下,只有 row 格式才能生效。(这也是为什么 RR 级别为默认隔离级别了,为了兼容 statement)

# 总结
格式 原理 优点 缺点
Statement-Based Replication(基于语句的复制) 记录执行的 SQL 语句,发送到从库执行 日志量相对少,简单操作传输效率高 依赖环境函数可能导致主从不一致,含 LIMIT <br><br > 子句且依赖数据顺序时可能出错
Row-Based Replication(基于行的复制) 记录每行数据变化,发送到从库应用 准确复制数据,避免多种主从不一致情况 日志量大,占用空间和带宽多
Mixed-Based Replication(混合模式的复制) 结合语句和行复制,自动切换 兼顾低日志量与高一致性 复制方式自动切换增加复杂性

# 实例验证

# 建表
CREATE TABLE `t1` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
insert into t1 values(10,1);
# 开启两个事务
Session 1 Session 2
set session transaction isolation level read committed;
set autocommit = 0; set session transaction isolation level read committed;
set autocommit = 0;
begin; begin;
delete from t1 where b < 100;
insert into t1 values(10,99);
commit;
commit;

正常情况下,两个事务执行后,数据库里面只会存在一条记录(10,99),这是因为当前的隔离级别是 RC,Session2 的插入操作不会看到 Session1 的删除操作,所以最后数据库中仍会保留 Session2 插入的记录。

以上两个事务执行后,binlog 文件会记录两条记录,因为事务 2 先提交,所以 binlog 文件内容参考如下:
image.png
接下来继续分析为什么在 RR 级别下不会出现不一致问题。

因为 RR 隔离级别不仅会对更新的数据行添加行级锁,还会增加 GAP 锁和临键锁。从而在事务 2 执行的时候,因为锁的原因导致事务 2 执行流程阻塞,需要等待事务 1 提交或者回滚后才能继续操作。

同时,MySQL 除了设置 RR 为默认的隔离级别,还禁止使用 statement 格式的 binlog 情况下,使用 RC 作为事务隔离级别。

用户主动修改隔离级别,尝试更新时,会报错:

ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'

# 为什么大厂一般使用 RC?

今天,面试官 Yes 哥群里问了一个问题 就是大厂为什么一般使用 RC,这里分析以下,首先了解下 RC 和 RR 的一些区别

对比维度 RR(可重复读) RC(读已提交)
一致性读 快照在事务中第一次 SELECT <br><br > 语句执行时生成,只有本事务中对数据进行更改才会更新快照 每次读取都会重新生成一个快照,总是读取行的最新版本;支持 “半一致读”, update <br><br > 语句中若 where <br><br > 条件匹配到的记录已加锁,InnoDB 会返回记录最近提交的版本,由 MySQL 上层判断是否真的加锁
锁机制 支持 Record Lock <br><br>(记录锁)、 Gap Lock <br><br>(间隙锁)和 Next-Key Lock <br><br>(记录锁与间隙锁的组合),以解决幻读问题 只会对索引增加 Record Lock <br><br>,不会添加 Gap Lock <br><br > 和 Next-Key Lock
主从同步 同时支持 statement <br><br>、 row <br><br > 以及 mixed <br><br > 三种 binlog 格式 只支持 row <br><br > 格式的 binlog;若指定 mixed <br><br > 作为 binlog 格式,使用 RC 时服务器会自动使用基于 row <br><br > 格式的日志记录
优点 - 保证同一事务内多次读取数据的一致性,有效避免不可重复读和部分幻读问题。 <br>- 适用于对数据一致性要求较高的场景,如金融交易等业务。 <br>- 支持多种 binlog 格式,在主从同步方面有更多选择。 - 相对较高的并发性能,每次读取最新数据,减少了数据的滞后性。 <br>- 适用于对数据实时性要求较高,而对一致性要求相对较低的场景,如多数互联网应用的读操作。
缺点 - 由于使用了 Gap Lock <br><br > 和 Next-Key Lock <br><br>,可能会导致锁竞争加剧,从而降低系统的并发性能。 <br>- 对于长事务,可能会长时间持有锁,影响其他事务的执行。 - 无法避免幻读问题,在并发事务中,可能会出现同一事务内多次读取结果不一致的情况,因为每次读取都是最新数据。 <br>- 只支持 row <br><br > 格式的 binlog,在主从同步时灵活性相对较差。

通过上面的对比,我们大概可以分析出来为什么使用 RC

  1. 提升并发

RC 在加锁的过程中,是不需要添加 Gap Lock 和 Next-Key Lock 的,只对要修改的记录添加行级锁就行了。
这就使得并发度要比 RR 高很多。

另外,因为 RC 还支持 "半一致读",可以大大的减少了更新语句时行锁的冲突;对于不满足更新条件的记录,可以提前释放锁,提升并发度。

  1. 减少死锁

因为 RR 这种事务隔离级别会增加 Gap Lock 和 Next-Key Lock,这就使得锁的粒度变大,那么就会使得死锁的概率增大。这样做也不是完全没有问题,首先使用 RC 之后,就需要自己解决不可重复读的问题,这个其实还好,很多时候不可重复读问题其实是可以忽略的,或者可以用其他手段解决。
比如读取到别的事务修改的值其实问题不太大的,只要修改的时候的不基于错误数据就可以了,所以我们都是在核心表中增加乐观锁标记,更新的时候都要带上锁标记进行乐观锁更新。

# 数据库的脏读、不可重复读和幻读分别是什么?

# 简要回答

# 1.1. 脏读

读到了其他事务还没有提交的数据

# 1.2. 不可重复读

对某个数据进行读取过程中,有其他事务对数据进行了修改(UPDATE,DELETE), 导致了第二次读取的结果不同

# 1.3. 幻读

事务在做范围查询过程中,有另外一个事务对范围内新增或删除了记录(INSERT,DELETE),导致范围查询的结果条数不一致。

# 扩展知识

# InnoDB 如何解决脏读、不可重复读、幻读的?

在 innoDB 中,通过 MVCC 解决脏读和不可重复读,通过 MVCC + 间隙锁解决幻读。

  1. 脏读的解决:“读已提交(Read Committed)” 隔离级别可解决脏读问题。事务执行读取操作时,InnoDB 获取当前最新全局事务 ID,该 ID 代表当前所有已提交事务的最新状态。InnoDB 检查数据行版本,仅当版本由小于或等于当前事务 ID 且已提交的事务修改时,该版本才可见,确保事务只能看到已提交的数据版本。
  2. 不可重复读的解决:InnoDB 通过多版本并发控制(MVCC)解决不可重复读问题。在 “可重复读(RR)” 隔离级别下,使用快照读时,仅在第一次读取时生成一个 Read View,后续快照读都使用同一快照,避免了不可重复读。
  3. 幻读的解决:InnoDB 的 “可重复读(RR)” 级别采用 MVCC + 间隙锁的方式,能在一定程度上避免幻读,但无法完全杜绝。当事务中发生当前读时,仍可能导致幻读。

# InnoDB 的 RR 是如何解决缓幻读,是否还有出现幻读的可能?

InnoDB 中,“可重复读(RR)” 隔离级别通过间隙锁和 MVCC 解决大部分幻读问题,但无法完全杜绝,彻底解决幻读需使用 “串行化(Serializable)” 隔离级别。具体如下:

  • RR 解决部分幻读问题

  • 当前读幻读:通过间隙锁将记录之间的间隙锁住,防止新数据插入,解决部分当前读的幻读问题。

  • 快照读幻读:利用 MVCC 机制,RR 中快照读仅首次进行数据查询,后续直接读取快照,避免了快照读的幻读。

  • RR 仍存在幻读场景

  • 先快照读再更新新记录:事务 1 先快照读,事务 2 插入记录并提交,事务 1 能更新新插入记录,出现幻读。

  • 先快照读,当前读之后再快照读:事务 1 先快照读,事务 2 插入记录并提交,事务 1 当前读之后再进行快照读,也会发生幻读 。

# 如何避免幻读?

  • 彻底解决:在 InnoDB 中,若要彻底解决幻读,只能采用 Serializable 隔离级别。

  • 一定程度解决或避免

  • 隔离级别选择:RR(可重复读)隔离级别可在一定程度上解决或避免幻读,而 RC(读已提交)、RU(读未提交)无法做到。

  • 操作建议

  • 使用快照读:在 RR 级别中,尽量使用快照读(无锁查询),既能减少锁冲突、提升并发度,还可避免幻读。

  • 尽早加锁:并发场景中若必须加锁,需在事务开始时立即加锁,利用间隙锁避免幻读,但要注意间隙锁可能引发死锁,使用需谨慎 。