文章

面试题学习笔记 | MySQL 事务隔离级别

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

在 MySQL 中,事务并发执行时可能会出现以下三种常见的并发问题:

  • 脏读(Dirty Read)

    指一个事务读取了另一个事务尚未提交的数据。如果该事务之后回滚,那么第一个事务读取到的数据就成为了脏数据。这种情况被称为脏读,意味着事务读取的数据不可靠

  • 不可重复读(Non-repeatable Read)

    在同一个事务内,读取同一条数据两次,但因为期间其他事务提交了对该数据的修改,导致两次读取的结果不一致。也就是说,相同的查询操作在同一事务中返回了不同的结果,这种现象被称为不可重复读。

  • 幻读(Phantom Read)

    在同一个事务内,执行相同的查询操作多次,但因为其他事务插入了新记录,导致查询结果集发生了变化。这种现象称为幻读,表现为在事务中多次查询时返回的记录数量发生了变化。

注意:不可重复读和幻读有时容易混淆。不可重复读侧重于同一条数据内容的变化,而幻读则是指数据总量的变化,通常是由于其他事务插入了新的记录

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

MySQL 中定义了四种事务隔离级别,按从低到高的顺序如下:

  1. 读未提交(READ UNCOMMITTED)

    这是最低的事务隔离级别。在该级别下,一个事务可以读取到其他事务尚未提交的数据。这种情况会导致脏读问题,因为事务可以看到不可靠的数据,可能会引发数据不一致的错误

  2. 读已提交(READ COMMITTED)

    在此级别下,事务只能读取其他事务已经提交的数据。虽然能够避免脏读问题,但仍然可能会遇到不可重复读问题。即在同一事务中,相同的查询操作可能会返回不同的结果

  3. 可重复读(REPEATABLE READ)

    在该隔离级别下,事务内多次执行的查询将始终返回相同的结果,从而避免了不可重复读问题。然而,在此级别下仍然可能会发生幻读现象,即多次查询可能返回不同数量的行。可重复读是 MySQL 默认的事务隔离级别

  4. 串行化(SERIALIZABLE)

    串行化是最高的事务隔离级别。它通过强制所有事务按顺序执行来确保事务的执行结果与这些事务按某一顺序串行执行的效果相同。这种级别能够避免所有并发问题,但性能开销极大,因为它会大幅降低并发性

扩展:事务隔离级别与并发问题的解决

  1. 为什么可重复读不能避免幻读的发生?

    为了解释这个问题,我们需要先了解 快照读当前读 的概念:

    • 快照读(Snapshot Read):事务在执行查询时,并不会直接读取最新的数据,而是读取数据的历史版本,即数据的快照。MySQL 通过多版本并发控制(MVCC)来实现快照读。可重复读隔离级别下,事务的第一次查询会创建一个数据快照,在之后的查询中都会复用这个快照数据。即使其他事务修改了数据,当前事务的读取也不会受到影响。快照读能够有效避免脏读和不可重复读。

    • 当前读(Current Read):当前读指的是读取数据的最新版本,并且会加锁来确保数据的一致性。当前读通常出现在 UPDATEDELETE 或带有锁的查询中,它通过加锁(如 Next-Key Locking)来锁定数据范围,防止其他事务在同一范围内插入新的记录。

    即便是在可重复读隔离级别下,由于当前读的存在,仍然可能会产生幻读。为了避免幻读,可以通过在查询时显式加锁,例如使用 SELECT ... FOR UPDATE,从而阻止其他事务插入新记录。

    另外,InnoDB 存储引擎在可重复读隔离级别中,已经通过间隙锁(Gap Lock)和临建锁(Next-Key Lock)机制,尽可能地避免了大部分幻读问题。

  2. 事务隔离级别的选择

    选择合适的事务隔离级别时,通常需要在并发性能和数据一致性之间找到平衡。较低的事务隔离级别能够提高并发性,但可能导致数据不一致;较高的事务隔离级别可以保障数据一致性,但会损失一些并发性

    • 可重复读(REPEATABLE READ) 是 MySQL 的默认事务隔离级别。该级别通过使用间隙锁和临建锁,避免了不可重复读问题,但仍可能产生幻读。对于大部分场景,MySQL 的可重复读隔离级别已经足够解决大多数并发问题

    • 读已提交(READ COMMITTED) 采用较小的锁粒度,因此能提高并发性,但可能会导致幻读。为了避免幻读问题,可以结合半一致性读优化,在执行 UPDATE 时如果发现当前行被锁定,则会执行半一致性读操作,从而确保数据的最新版本被正确更新

    • 串行化(SERIALIZABLE) 虽然能够解决所有并发问题,但因为强制事务按顺序执行,导致性能开销非常大。除非对事务一致性要求极高,否则一般不推荐使用此级别

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

MySQL 默认的事务隔离级别是 可重复读(REPEATABLE READ, RR)

选择该隔离级别的原因之一是为了兼容早期的 binlog(Binary Log)statement 格式。在使用 statement 格式 的 binlog 时,如果选择读未提交或读已提交等隔离级别,可能会导致主从数据库数据不一致问题,因为这两种隔离级别可能会导致事务的提交顺序发生变化,从而导致 binlog 记录的 SQL 执行顺序不一致

为了避免这个问题,MySQL 选择了可重复读隔离级别,因为该级别通过间隙锁和临建锁,能够保证事务提交的顺序一致,避免了 binlog 重放时的主从数据不一致问题

总结

MySQL 提供了四种事务隔离级别,通过不同程度的锁定机制来平衡数据一致性和系统性能。在实际应用中,根据业务需求选择合适的隔离级别,可以在保证数据一致性的同时,最大化地提高系统的并发性能

License:  CC BY 4.0