事务的隔离级别

困难只是暂时的,放弃才是永久的。
—— 罗伯特·舒勒

当多个事务并发执行时可能会遇到 脏读、不可重复读、幻读 的现象,这些现象会对事务的一致性产生不同程度的影响。

  • 脏读:读到其它事务未提交的数据
  • 不可重复读:前后读取的数据不一致
  • 幻读:前后读取的记录数量不一致

这三个现象的严重性排序:

脏读 > 不可重复读 > 幻读

SQL标准提出了四种隔离级别来规避这些现象,隔离级别越高,性能效率就越低:

读未提交(read uncommitted):指一个事务还没提交时,它做的变更就能被其它事务看到。

读提交(read committed):指一个事务提交之后,它做的变更才能被其它事务看到。

可重复读(repeatable read): 指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB引擎的默认隔离级别

串行化(serializable):会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

按隔离水平高低排序如下:

串行化 > 可重复读 > 读已提交 > 读未提交

针对不同的隔离级别,并发事务时可能发生的现象也会不同

隔离级别 脏读(Dirty Read) 不可重复读(Non-Repeatable Read) 幻读(Phantom Read) 典型应用场景
READ UNCOMMITTED ✅ 可能 ✅ 可能 ✅ 可能 极少使用(数据准确性要求低)。
READ COMMITTED ❌ 避免 ✅ 可能 ✅ 可能 Oracle 默认级别,允许不可重复读。
REPEATABLE READ ❌ 避免 ❌ 避免 ✅ 可能(InnoDB 通过 MVCC 部分避免) MySQL 默认级别,平衡性能与一致性。
SERIALIZABLE ❌ 避免 ❌ 避免 ❌ 避免 严格一致性需求(如金融交易)。
  • InnoDB 在 REPEATABLE READ 下通过 MVCC + Next-Key Lock 避免大部分幻读问题。
  • 隔离级别越高,并发性能越低(锁竞争增加)。

所以要解决脏读现象,就要升级到 读提交 以上的隔离级别;要解决不可重复读现象,就要升级到 可重复读 的隔离级别,要解决幻读现象不建议将隔离级别升级到 串行化

不同的数据库厂商对SQL标准中规定的4种隔离级别的支持不一样,有的数据库只实现了其中几种隔离级别,我们讨论的MySQL虽然支持4种隔离级别,但是与SQL标准中规定的各级隔离级别允许发生的现象有些出入

MySQL在 可重复读 隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),所以MySQL并不会使用 串行化 隔离级别来避免幻读现象的发生,因为使用 串行化 隔离级别会影响性能。

MySQL InnoDB引擎的默认隔离级别虽然是 可重复读 ,但是他很大程度上避免幻读现象(并不是完全解决),解决的方案有两种:

  • 针对快照读(普通select 语句),是通过MVCC方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其它事务插入了一条数据,是查询不出来这条数据的,所以就很好避免幻读问题。
  • 针对当前读(select ... for update等语句),是通过 next-key lock(记录锁 + 间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其它事务在next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好的避免幻读问题。

四种隔离级别具体是如何实现的呢

对于 读未提交 隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;

对于 串行化 隔离级别的事务来说,通过加读写锁的方式来避免并行访问;

对于 读提交可重复读 隔离级别的事务来说,它们是通过 Read View,它们的区别在于创建Read View的时机不同,读提交 隔离级别是在每个语句执行前都会重新生成一个Read View,而 可重复读 隔离级别是 启动事务 时生成一个 Read View,然后整个事务期间都在用这个 Read View

注意,执行 开始事务 命令,并不意味着启动了事务。在MySQL有两种开启事务的命令,分别是:

  • 第一种:begin / start transaction 命令
  • 第二种:start transaction with consistent snapshot 命令

这两种开启事务的命令,事务的启动时机是不同的:

  • 执行了 begin / start transaction 命令后,并不代表事务启动了。只有在执行这个命令后,执行了第一天select 语句,才是事务真正启动的时机;
  • 执行了start transaction with consistent snapshot 命令,就会马上启动事务。

原文

posted @ 2025-04-04 14:14  Tsukinor  阅读(39)  评论(0)    收藏  举报