MySQL InnoDB事务隔离级别

155 阅读8分钟

前言

事务之间的隔离是确保事务安全性的重要手段之一。IOS 与 ANIS SQL 标准制定了四种事务隔离级别的标准,但是很少有数据库厂商遵循这些标准,比如 Oracle 数据库就不支持 读未提交可重复读

SQL 标准定义的四种隔离级别:

  1. 读未提交(READ UNCOMMITTED)
  2. 读已提交(READ COMMITTED)
  3. 可重复读(REPEATABLE READ)
  4. 串行化(SERIALIZABLE)

隔离级别依次递增。

隔离级别越低,事务请求锁或者保持锁的时间就越短,所以你可以看到大部分数据库的默认隔离级别是 读已提交

InnoDB 隔离级别

读未提交

可以读取到其他事务中未提交的脏数据。这种模式下对事务的数据正确性就没有什么保障,比如,未提交的事务可能多次修改,亦或者发生异常进行了回滚,从而导致使用了不正常的数据。

应用场景:对于那些数据准确性不高的场景,比如 日志记录、实时日志分析可以使用。

当然,由于数据的准确性没有保障,大部分场景下都不太适用。因此,虽然很多数据库支持这种隔离级别,你可以发现的是基本没有数据库将其设置为默认的数据库隔离级别。

读已提交

既然 读未提交 会读取到脏数据,那么我们想办法读取到其他事务已提交的数据,这样就不会读取到其他事务中间态的数据而导致的脏数据问题,这种模式就是 读已提交

那这种模式事务就完美解决问题了?

非也,比如 不可重复度问题。

不可重复读问题

假设事务 A 中需要两次读取记录 R 的值,首次读取 R='aaa', 然后事务 B 更改 R='bbb';此时,事务A再次读取 R='bbb',即 在同一个事务中两次读取的数据不一致

试想,你的业务逻辑是当某个值等于xxx时才能执行,首次判断没问题,你执行业务操作;而当你在同一个事务中再次判断时,对应的值已经不满足条件了,此时已执行的业务动作已没法撤回,从而导致了异常的业务结果。

你想说,为什么不通过锁来控制对同一条记录的访问,从而确保同一事务时间要么都只能访问,要么等修改之后才能访问?

当然可以,不过这就不是读已提交的实现范畴了...

可重复读

我们在 读已提交 的基础上,使用手段控制同一个事务中,前后读取的数据保持一致,这就是 可重复读

怎么来实现?

InnoDB 提供了多版本并发控制(MVCC)解决方案,也叫一致性非锁定读,顾名思义,在不加锁的前提下确保在一个事务中前后读取的数据是一致性的,也尽可能的保障了并发性能问题。

本质来讲,MVCC 使用版本号机制,每个事务都持有数据记录的版本,每个版本的修改通过链表形式串联起来,形成一个递增的版本链,当你需要之前的数据版本时,利用 undo log 日志 + 当前最新数据 进行往前回滚,即可获取到对应版本的数据。

值得注意的是,InnoDB 的 读已提交隔离级别下,也使用了 MVCC,不同点在于读已提交总是读取锁定行的最新一份版本数据;而可重复读隔离级别下,总是读取每个事务开始的行数据版本。

可重复读的隔离级别下,出现了新的问题 ---- 幻读

为了解决 幻读 问题,InnoDB 实现下,引入 Next-key Lock锁来解决。

幻读

什么是幻读?事务A在读取的同时,事务B删除或者新增了一条相同的数据,导致事务A以相同的条件前后读取的数据不一致。

其实,在事务隔离级别的标准定义中,并未要求解决幻读问题,InnoDB 在可重复度这个隔离级别中解决了幻读问题,让其适用场景更加普适性。

你可以看到的是,InnoDB 默认支持的隔离级别就是 可重复读

Next-key Lock 锁

InnoDB 的 Next-Key Lock 是 MySQL 中 InnoDB 存储引擎用于解决幻读问题的一种锁机制,它结合了行锁和间隙锁的特点。

基本概念:

  • 行锁:锁定表中的某一行数据,防止其他事务对该行数据进行修改、删除等操作。
  • 间隙锁:锁定索引记录之间的间隙,防止其他事务在这些间隙中插入新的数据。
  • Next-Key Lock:是行锁和间隙锁的组合,既锁定索引记录本身,又锁定索引记录之间的间隙。

原理:

当使用可重复读隔离级别时,InnoDB 默认使用 Next-Key Lock 来处理查询。

当一个事务执行一个范围查询(如 WHERE column BETWEEN a AND b)时,InnoDB 会对满足条件的索引记录加行锁,同时对这些记录之间的间隙加间隙锁,从而防止其他事务在该范围内插入新的数据,避免幻读问题。

InnoDB 在可重复度的隔离级别下,利用 Next-key Lock 锁,确保了对同一条记录的操作;也就是说,此隔离级别下,已达到了 SQL 标准定义的 串行化 隔离级别标准

串行化

什么是串行化?

类似于千军万马过独木桥,同一时间只能一个人过桥,大家都排队一依次通过。串行化隔离级别也是这样,对操作同一条数据记录的事务,排队依次进行处理。

串行化提供了最高等级的数据一致性,会强制事务串行执行,完全避免了脏读、不可重复读和幻读等问题。所有事务就像依次执行一样,不存在并发冲突。

可以看到这种方式没有并发性,效率最低。

读 与 读 可以并发处理?

读读操作是可以并发处理的,但读写、写写操作会被严格串行化。

不同数据记录的写操作,串行化不能并发执行?

在串行化隔离级别下,不同数据记录的写操作通常也不能并发执行。串行化隔离级别旨在提供最高程度的数据一致性,它要求事务完全串行执行,就像在单线程环境下依次处理每个事务一样。

在写操作时,数据库会为每个写操作加上排他锁(写锁),排他锁具有独占性,同一时间只能有一个事务持有该锁

可重复读 + next-key lock 实现下,与串行化隔离级别区别?

数据一致性保障

  • 可重复读 + Next - Key Lock:主要目标是解决幻读问题,保证在同一个事务里多次执行相同的查询时,得到的结果集一致。不过,它对于不同事务间数据的读写顺序并没有严格的限制,有可能出现写写冲突的情况。
  • 串行化隔离级别:提供了最高等级的数据一致性,会强制事务串行执行,完全避免了脏读、不可重复读和幻读等问题。所有事务就像依次执行一样,不存在并发冲突。

锁的使用方式

  • 可重复读 + Next - Key Lock:仅在需要的地方加锁,也就是在进行范围查询时对索引记录和间隙加锁。对于非范围查询,可能只加行锁,其他事务仍可对未加锁的数据进行操作。
  • 串行化隔离级别:会对所有的读操作加共享锁,对写操作加排他锁。在事务执行期间,其他事务无法对相关数据进行读写操作,相当于把整个事务执行过程当作一个整体进行串行化。

并发性能

  • 可重复读 + Next - Key Lock:在保证一定数据一致性的前提下,允许一定程度的并发操作。只要事务操作的数据范围不冲突,多个事务可以同时执行,因此并发性能相对较好。
  • 串行化隔离级别:由于事务是串行执行的,同一时间只能有一个事务对数据进行操作,这会导致并发性能大幅下降,在高并发场景下容易出现性能瓶颈。

总结

MySQL InnoDB 事务的四种隔离级别,分别提供了不同宽松程度的限制:

  1. 读未提交:直接的问题是产生 脏读 问题
  2. 读已提交:解决了读未提交的脏读问题,但仍然没有解决 不可重复读 问题
  3. 可重复读:解决了不可重复读问题,但仍然存在 幻读问题;在 InnoDB 的实现下,引入 Next-key Lock 锁解决幻读问题
  4. 串行化:隔离程度最高,完全解决了脏读、不可重复度、幻读问题,但性能较低。