mysql菜鸟教程
15.4 事务隔离级别
在上一节中,我们学习了事务的 ACID 特性,其中 隔离性(Isolation) 指的是多个事务并发执行时,一个事务的执行不应被其他事务干扰。然而,完全隔离事务往往意味着性能的极大损失,因为事务必须串行执行。因此,数据库系统提供了不同的隔离级别,允许开发者在数据一致性和并发性能之间做出权衡。
一、为什么需要隔离级别?
当多个事务同时操作同一份数据时,如果不加控制,可能会出现以下三种常见的并发问题:
为了解决这些问题,SQL 标准定义了四个隔离级别。级别越高,数据一致性越好,但并发性能越低。
二、四种隔离级别
下面我们详细介绍每种级别,并用示例说明其行为。
1. 读未提交(READ UNCOMMITTED)
在此级别下,一个事务可以看到其他事务未提交的修改。这可能导致脏读。
示例:
事务 A 和事务 B 同时进行。
事务 A 开始,将 account 表中张三的余额从 1000 改为 900(未提交)。
事务 B 开始,读取张三的余额,得到 900(脏读)。
事务 A 回滚,余额恢复为 1000。
事务 B 后续操作基于错误的 900 进行,产生错误。
使用场景:几乎不用,除非对数据一致性要求极低且追求极致性能(比如某些统计类应用)。
2. 读已提交(READ COMMITTED)
在此级别下,一个事务只能看到其他事务已经提交的修改。这避免了脏读,但可能出现不可重复读。
示例:
事务 A 开始,查询张三余额,得到 1000。
事务 B 开始,将张三余额改为 900 并提交。
事务 A 再次查询张三余额,得到 900(不可重复读,因为两次读取结果不同)。
使用场景:许多数据库的默认级别,适合对一致性有一定要求但允许短暂不一致的业务。
3. 可重复读(REPEATABLE READ)
在此级别下,一个事务在执行期间看到的数据始终保持一致。即第一次读取后,后续再读同一行,结果不变。这避免了脏读和不可重复读。
MySQL 的 InnoDB 引擎通过 多版本并发控制(MVCC) 实现可重复读。在可重复读级别下,InnoDB 还通过 间隙锁(gap lock) 机制部分解决了幻读问题,使得大多数情况下幻读不会发生。
示例:
事务 A 开始,查询工资大于 5000 的员工,返回 10 行。
事务 B 开始,插入一个工资 8000 的员工并提交。
事务 A 再次查询工资大于 5000 的员工,仍然只看到 10 行(没有幻读,因为 InnoDB 的间隙锁阻止了插入)。
但在某些极端的并发场景下,仍可能发生幻读。如果绝对避免幻读,需要提升到可串行化。
使用场景:MySQL 默认级别,适合大多数业务,特别是需要一致性读的场景(如报表生成、统计分析)。
4. 可串行化(SERIALIZABLE)
此级别强制事务串行执行,所有并发问题都得以避免。实现方式通常是对所有读取的数据加锁(包括间隙锁),这会导致并发性能急剧下降。
示例:
事务 A 开始,查询工资大于 5000 的员工,会对满足条件的行以及间隙加锁。
事务 B 试图插入工资 8000 的员工,会被阻塞,直到事务 A 提交或回滚。
使用场景:对数据一致性要求极其严格,且并发量很低的场景(如金融交易中的某些关键操作)。
三、在 MySQL 中设置隔离级别
查看当前隔离级别
-- 查看全局或会话的隔离级别 SELECT @@global.transaction_isolation; SELECT @@session.transaction_isolation; -- 或老版本语法 SELECT @@global.tx_isolation; SELECT @@session.tx_isolation;
设置隔离级别
-- 设置会话级(仅当前会话有效) SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 设置全局级(所有新会话生效) SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
示例:测试不同隔离级别的行为
为了直观感受不同隔离级别的差异,可以开启两个 MySQL 客户端窗口(会话 A 和会话 B)进行实验。
准备数据:
CREATE TABLE account ( id INT PRIMARY KEY, balance INT ); INSERT INTO account VALUES (1, 1000);
实验1:读未提交下的脏读
会话 A:SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
会话 B:SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
会话 A:START TRANSACTION; UPDATE account SET balance = 900 WHERE id = 1;
会话 B:SELECT * FROM account; 可以看到 balance = 900(脏读)
会话 A:ROLLBACK; 此时会话 B 读到的 900 是无效的。
实验2:读已提交下避免脏读,但出现不可重复读
会话 A:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
会话 B:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
会话 A:START TRANSACTION; SELECT * FROM account; 得到 1000。
会话 B:START TRANSACTION; UPDATE account SET balance = 900 WHERE id = 1; COMMIT;
会话 A:再次 SELECT * FROM account; 得到 900(不可重复读)。
实验3:可重复读下避免不可重复读
会话 A:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
会话 B:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
会话 A:START TRANSACTION; SELECT * FROM account; 得到 1000。
会话 B:UPDATE account SET balance = 900 WHERE id = 1; COMMIT;
会话 A:再次 SELECT * FROM account; 仍然得到 1000(因为 MVCC 提供了事务开始时的快照)。此时会话 B 的修改对会话 A 不可见,直到会话 A 结束事务。
小结
隔离级别定义了事务之间数据可见性的程度,需要在一致性和性能之间权衡。
读未提交:允许脏读,几乎不用。
读已提交:避免脏读,但可能出现不可重复读和幻读。
可重复读:MySQL 默认级别,避免脏读和不可重复读,InnoDB 通过间隙锁基本避免幻读。
可串行化:完全隔离,但性能最低。
使用 SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL 可以调整隔离级别。
理解隔离级别有助于分析和解决并发环境下的数据一致性问题。

发表评论
所有评论