MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MySQL事务隔离级别详解与实践

2021-11-231.3k 阅读

MySQL事务隔离级别概述

在MySQL数据库中,事务是一个不可分割的工作单元,它确保一组数据库操作要么全部成功执行,要么全部失败回滚。事务隔离级别则定义了一个事务与其他并发事务之间的隔离程度,不同的隔离级别会影响到数据的一致性和并发性能。MySQL提供了四种事务隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

读未提交(Read Uncommitted)

原理

读未提交是最低的隔离级别,在这种级别下,一个事务可以读取到另一个未提交事务修改的数据。这意味着,如果事务A正在修改数据但尚未提交,事务B就可以读取到这些未提交的修改。这种隔离级别存在脏读(Dirty Read)的问题,即读取到了未提交的数据,而这些数据可能最终会被回滚,导致数据的不一致性。

代码示例

首先,创建一个测试表:

CREATE TABLE test_table (
    id INT PRIMARY KEY AUTO_INCREMENT,
    value VARCHAR(50)
);

开启两个MySQL客户端会话,模拟并发事务。

会话1:

START TRANSACTION;
INSERT INTO test_table (value) VALUES ('initial value');
-- 此时数据未提交

会话2:

-- 设置隔离级别为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM test_table; 
-- 这里会读取到会话1中未提交的插入数据

会话1:

ROLLBACK;

会话2再次查询:

SELECT * FROM test_table; 
-- 由于会话1回滚,此时查询结果为空,体现了脏读问题

读已提交(Read Committed)

原理

读已提交隔离级别解决了脏读的问题。在这种级别下,一个事务只能读取到已经提交的事务修改的数据。当一个事务执行查询操作时,它只能看到其他事务已经提交的修改,而看不到未提交的修改。但是,读已提交存在不可重复读(Non - Repeatable Read)的问题,即在同一个事务中,多次读取同一数据时,可能会因为其他事务的提交而得到不同的结果。

代码示例

同样使用上述创建的test_table表。

会话1:

START TRANSACTION;
SELECT * FROM test_table WHERE id = 1;
-- 假设查询到初始值

会话2:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE test_table SET value = 'new value' WHERE id = 1;
COMMIT;

会话1:

SELECT * FROM test_table WHERE id = 1;
-- 此时会读取到会话2提交后的新值,体现了不可重复读问题

可重复读(Repeatable Read)

原理

可重复读隔离级别进一步提升了数据的一致性。在这种级别下,一个事务在整个执行过程中,多次读取同一数据时,看到的数据是一致的,不会因为其他事务的提交而改变。这是通过MySQL的MVCC(多版本并发控制)机制来实现的。MVCC为每个数据行维护多个版本,事务在读取数据时,根据事务开始时的系统版本号来确定读取哪个版本的数据,从而保证了在事务内读取数据的一致性。然而,可重复读虽然解决了不可重复读的问题,但在某些情况下,可能会出现幻读(Phantom Read)的现象。幻读是指在一个事务中,多次执行相同的查询条件,却得到了不同数量的结果集,原因是其他事务插入了符合查询条件的新数据。

代码示例

还是基于test_table表。

会话1:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM test_table WHERE value LIKE 'new%';
-- 假设当前没有符合条件的数据

会话2:

START TRANSACTION;
INSERT INTO test_table (value) VALUES ('new data');
COMMIT;

会话1:

SELECT * FROM test_table WHERE value LIKE 'new%';
-- 在可重复读隔离级别下,这里仍然不会读取到会话2插入的数据,避免了不可重复读

但是,如果会话1执行插入操作后再查询:

INSERT INTO test_table (value) VALUES ('new data 2');
SELECT * FROM test_table WHERE value LIKE 'new%';
-- 此时会读取到会话2插入的数据,出现幻读现象

串行化(Serializable)

原理

串行化是最高的隔离级别,它通过强制事务串行执行来避免所有的并发问题,包括脏读、不可重复读和幻读。在串行化隔离级别下,每个事务都在独立的环境中执行,就好像没有其他事务在同时运行一样。这是通过对所有涉及的表加锁来实现的,在一个事务执行期间,其他事务对相关表的读写操作都将被阻塞,直到该事务提交或回滚。虽然串行化保证了数据的最高一致性,但由于并发性能极低,通常只在对数据一致性要求极高且并发量较小的场景下使用。

代码示例

使用test_table表。

会话1:

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM test_table;
-- 此时对表加锁

会话2:

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
INSERT INTO test_table (value) VALUES ('new data');
-- 这里会被阻塞,直到会话1提交或回滚

如何选择合适的事务隔离级别

  1. 读未提交:几乎不会在实际生产环境中使用,因为脏读问题严重影响数据一致性。但在一些对数据一致性要求极低,且追求极致并发性能的场景,如某些统计类的临时查询,可能会考虑使用。
  2. 读已提交:是大多数数据库的默认隔离级别,适用于大部分业务场景,它能保证数据不会出现脏读,同时在并发性能上也有较好的表现。例如,在电商系统中,订单查询等操作可以使用读已提交隔离级别,用户查询订单时不会看到未提交的订单修改。
  3. 可重复读:在MySQL中,可重复读是默认的隔离级别。它适用于对数据一致性要求较高,同时需要一定并发性能的场景。例如,在银行转账业务中,确保在一个事务内多次读取账户余额时数据一致非常重要,可重复读可以满足这种需求。
  4. 串行化:适用于对数据一致性要求极高,并发量较小的场景。比如在一些涉及金融核心数据的操作,如银行清算等,确保数据的绝对一致性比并发性能更为重要。

事务隔离级别对性能的影响

  1. 读未提交:由于几乎没有任何隔离限制,并发性能最高,但数据一致性最差。因为它允许脏读,所以在并发事务操作时,不需要额外的锁机制或版本控制来保证数据一致性,因此性能开销最小。
  2. 读已提交:比读未提交增加了对已提交数据的读取限制,避免了脏读。在实现上,需要通过一定的机制(如MVCC)来保证读取到已提交的数据版本。相比于读未提交,性能会有所下降,但仍然能满足较高的并发需求。
  3. 可重复读:在保证不可重复读的基础上,通过MVCC机制为事务提供了一致的读视图。这意味着在事务执行期间,读取的数据版本相对固定。由于MVCC的实现需要维护数据的多个版本,所以性能开销比读已提交更大。但在大多数情况下,它能在数据一致性和并发性能之间取得较好的平衡。
  4. 串行化:通过对表加锁来实现事务的串行执行,虽然保证了数据的最高一致性,但并发性能最低。因为在一个事务执行期间,其他事务对相关表的读写操作都被阻塞,这会导致大量的等待时间,严重影响系统的并发处理能力。

总结事务隔离级别与锁机制及MVCC的关系

  1. 读未提交:基本不涉及锁机制和MVCC的复杂应用,因为它允许读取未提交数据,不需要保证数据的一致性视图,所以对锁和版本控制的依赖较小。
  2. 读已提交:在读取数据时,通过MVCC获取已提交的数据版本。在写操作时,会使用锁机制来保证数据的一致性。例如,在更新数据时,会对相关数据行加排他锁,防止其他事务同时修改。
  3. 可重复读:主要依赖MVCC来实现事务内的一致性读。事务开始时,会获取一个系统版本号,在事务执行期间,根据这个版本号来读取数据版本。写操作同样会使用锁机制,并且在MySQL中,可重复读隔离级别下,对于普通查询(非锁定读)使用MVCC,而对于需要更新数据的操作,会对相关数据行加锁,以保证数据一致性。
  4. 串行化:主要依靠锁机制来实现事务的串行执行。对读操作会加共享锁,对写操作会加排他锁,并且在事务执行期间,这些锁会一直保持,直到事务结束。MVCC在串行化隔离级别下的作用相对较小,因为事务是串行执行,不需要通过版本控制来保证并发一致性。

在实际应用中,深入理解MySQL的事务隔离级别、锁机制和MVCC的关系,对于优化数据库性能、保证数据一致性至关重要。根据不同的业务场景,合理选择事务隔离级别,可以在满足数据一致性要求的同时,最大化系统的并发处理能力。