mysql 事物隔离级别 与mvcc
隔离级别
在 MySQL 中,事务隔离级别和MVCC (多版本并发控制) 是与并发性、数据一致性和性能密切相关的两个重要概念。它们共同决定了数据库在多个事务并发执行时如何处理数据的读写冲突。
1. 事务隔离级别 (Transaction Isolation Levels)
MySQL 支持四种事务隔离级别,它们定义了事务在执行时如何与其他并发事务进行交互,尤其是在读取数据时:
-
READ UNCOMMITTED(读未提交):
- 最低级别的隔离,事务可以读取其他事务未提交的更改。
- 可能出现脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)等问题。
-
READ COMMITTED(读已提交):
- 事务只能读取其他已提交事务的更改。
- 不可重复读问题仍然存在,但不允许脏读。
-
REPEATABLE READ(可重复读):
- 事务读取的数据在整个事务期间是稳定的,不会发生变化,即使其他事务提交了更改。
- 解决了脏读和不可重复读问题,但可能会出现幻读。
-
SERIALIZABLE(串行化):
- 最高的隔离级别,事务会被完全串行化执行,即使是读取操作也会被锁定,避免所有并发冲突。
- 解决了所有的并发问题,包括脏读、不可重复读和幻读,但性能开销较大。
事务隔离级别的选择会影响数据库并发性能和一致性要求。一般情况下,REPEATABLE READ 是 MySQL 默认的隔离级别,能够提供较好的平衡。
2. MVCC (多版本并发控制)
MVCC 是 MySQL 用来实现并发控制的机制。MVCC 允许多个事务并发执行,并尽量减少冲突,使得事务之间不需要频繁的锁操作,从而提高性能。
在 MVCC 中,每个数据行都有多个版本,事务在执行时操作的是数据的一个特定版本,而不是数据库表中的最新数据。这样,每个事务可以保持对自己操作数据的一个一致视图,避免了事务之间的相互影响。
MVCC 的核心原理是通过以下两点来实现:
-
版本控制:每行数据都会有一个版本标识(如
trx_id
和时间戳),用来标记某行数据在哪个事务中被修改过。当事务开始时,数据库会记录事务开始的时间戳或 ID,查询时会根据该事务的时间戳来决定读取哪个版本的数据。 -
快照读取:在使用 REPEATABLE READ 隔离级别时,MVCC 会为每个事务提供一个一致的视图。也就是说,事务中的查询操作会读取事务开始时的数据快照,而不是后续事务可能修改的数据。
3. MVCC 与事务隔离级别的关系
-
READ UNCOMMITTED:MVCC 并不影响这种级别的事务隔离,因为该级别允许读取未提交的数据(脏读),因此事务之间不会维护严格的版本控制。
-
READ COMMITTED:MVCC 仍然可以工作,但因为每次读取的数据都需要根据最新提交的版本来确定,所以不会存在脏读,但不可重复读问题依然存在。
-
REPEATABLE READ:这是 MySQL 默认的隔离级别,MVCC 会通过多版本控制来确保事务读取的一致性。即使其他事务提交了更改,当前事务也会读取自己事务开始时的快照数据,避免了不可重复读的问题,但可能会有幻读问题。
-
SERIALIZABLE:在这个级别,MVCC 仍然控制数据的版本,但由于事务会强制串行执行,它实际上限制了并发,因此这个级别的性能较低。虽然它避免了脏读、不可重复读和幻读,但代价较高。
4. MVCC 的实现
在 MySQL 中,尤其是使用 InnoDB 存储引擎时,MVCC 通过隐藏的系统列来实现多版本控制。这些列记录了每一行数据的创建事务 ID和删除事务 ID(如果有)。具体来说:
- trx_id:表示修改该行数据的事务的 ID。
- roll_pointer:指向被删除行的历史版本。
当一个事务修改数据时,InnoDB 会创建一个新的数据版本,并用新的事务 ID 更新该行的元数据。而其他事务可以继续读取该数据行的旧版本,从而保证一致性。
总结
- 事务隔离级别决定了事务如何与并发事务交互,影响数据的可见性和一致性。
- MVCC 是一种并发控制机制,它通过维护多个数据版本来允许并发事务读取不同版本的数据,并且通过时间戳和事务 ID 来确保事务操作的数据一致性。
- 在 MySQL 中,尤其是 InnoDB 引擎,REPEATABLE READ 隔离级别结合 MVCC 机制,提供了较好的并发性能和一致性保证。
事务隔离级别的常见并发问题
在 MySQL 中,脏读(Dirty Read)、不可重复读(Non-repeatable Read)、幻读(Phantom Read) 是事务隔离级别的常见并发问题,其中 脏读(Dirty Read) 是最基本的一种问题。理解这些问题的产生有助于掌握事务隔离级别的选择及其影响。
1. 脏读(Dirty Read)
脏读是指一个事务读取了另一个事务未提交的数据。换句话说,事务 A 读取了事务 B 修改的数据,而事务 B 还没有提交。如果事务 B 最终回滚,那么事务 A 就读取了“脏数据”,这种数据是不可靠的。
例子:
- 事务 A 修改了一条记录但未提交。
- 事务 B 在事务 A 提交之前读取了事务 A 修改的数据。
- 事务 A 如果回滚,事务 B 就会读取到一个不存在的数据(脏数据)。
脏读的危害:如果事务 B 基于脏数据做了进一步的计算或操作,最终可能导致数据的不一致性。
产生脏读的事务隔离级别:
- READ UNCOMMITTED(读未提交):这是最低的隔离级别,允许事务读取其他事务未提交的数据,因此容易发生脏读。
解决脏读的方法:
- 使用更高的事务隔离级别(如 READ COMMITTED 或更高),这样事务只会读取已提交的数据,避免脏读。
2. 不可重复读(Non-repeatable Read)
不可重复读是指在一个事务内,两次读取同一数据时,数据的值不同。这通常发生在事务中间,其他事务修改了该数据,并提交了更改。尽管事务 A 多次读取同一数据,但由于事务 B 已提交更改,导致事务 A 读取的数据发生了变化。
例子:
- 事务 A 读取一条记录(值为 10)。
- 事务 B 修改该记录,将其更新为 20,并提交。
- 事务 A 再次读取同一条记录,此时它读取到的值是 20,而不是 10。
不可重复读的危害:可能导致事务 A 在两次读取之间得出不同的结论,从而导致数据的不一致性,尤其是当事务 A 基于读取结果做进一步操作时。
产生不可重复读的事务隔离级别:
- READ COMMITTED(读已提交):事务只能读取其他事务已提交的最新数据,但仍然可能遇到不可重复读。
解决不可重复读的方法:
- 使用 REPEATABLE READ 隔离级别,保证事务期间对数据的读取是一致的,即使其他事务提交了修改。
3. 幻读(Phantom Read)
幻读是指在一个事务内,两次查询返回不同的结果集。这种情况发生在一个事务在第一次查询后,另一事务对数据进行了插入、删除或修改,导致第一次查询和第二次查询的结果集不同。与不可重复读不同,幻读影响的是查询结果的记录数,而非某一具体记录的值。
例子:
- 事务 A 查询符合某个条件的记录,返回了 10 条结果。
- 事务 B 插入了一条符合条件的新记录并提交。
- 事务 A 再次查询同样条件的记录,这时返回了 11 条记录。
幻读的危害:幻读可能会导致事务在读取数据时,不小心“漏掉”或“重复”了一些数据,从而导致数据不一致。
产生幻读的事务隔离级别:
- REPEATABLE READ(可重复读):虽然 REPEATABLE READ 可以避免脏读和不可重复读,但它并不能完全避免幻读,因为事务 A 可能会看到其他事务插入的新记录。
解决幻读的方法:
- 使用 SERIALIZABLE(串行化)隔离级别,该级别会锁定读取的记录,防止其他事务插入新的数据行,从而避免幻读。
4. 事务隔离级别与并发问题
MySQL 的事务隔离级别决定了如何处理这些并发问题,具体如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 允许脏读 | 允许不可重复读 | 允许幻读 |
READ COMMITTED | 不允许脏读 | 允许不可重复读 | 允许幻读 |
REPEATABLE READ | 不允许脏读 | 不允许不可重复读 | 允许幻读 |
SERIALIZABLE | 不允许脏读 | 不允许不可重复读 | 不允许幻读 |
- READ UNCOMMITTED 允许所有并发问题,提供最低的数据一致性保障。
- READ COMMITTED 避免了脏读,但仍可能发生不可重复读和幻读。
- REPEATABLE READ 能够避免脏读和不可重复读,但仍然可能发生幻读。
- SERIALIZABLE 能避免所有并发问题,但会牺牲性能,导致事务串行执行。
总结
- 脏读:一个事务读取另一个事务未提交的数据,可能导致数据不一致。
- 不可重复读:同一事务中,读取的同一数据在多次查询时发生变化,可能导致不一致性。
- 幻读:一个事务在多次查询之间,查询结果集发生变化,可能漏掉或重复某些数据。
为了避免这些并发问题,事务隔离级别需要根据应用场景做权衡,通常 REPEATABLE READ 是 MySQL 的默认隔离级别,它避免了脏读和不可重复读,但仍可能发生幻读。如果需要确保完全的数据一致性,可以使用 SERIALIZABLE 隔离级别,但性能开销较大。