交互式图解 — 从并发问题到隔离原理,完整理解
先理解事务,再理解为什么需要隔离
想象一家银行,多个柜员同时处理不同客户的账户操作。
如果没有任何隔离:柜员A正在帮客户A转账,柜员B同时读取了客户A的账户余额 —— 此时B看到的是"转账中"的脏数据,导致错误决策。
隔离级别就是数据库定义的一套规则,用来控制:
「当多个事务同时运行时,一个事务对另一个事务的可见程度」
事务是一组操作的逻辑单元,满足 ACID 特性:
A 原子性 要么全做,要么全不做
C 一致性 前后数据合法
I 隔离性 并发互不干扰
D 持久性 提交后永久生效
多个事务并发执行时,如果不加控制,会出现三类异常读问题:
❌ 脏读:读到别人未提交的数据
❌ 不可重复读:同一行两次读值不同
❌ 幻读:两次查询行数不同
隔离级别越高,数据越安全,但并发性能越低(锁越多)。
这是一个经典的一致性 vs 性能取舍问题。
不同业务场景需要选择合适的隔离级别。
MySQL InnoDB 默认使用 可重复读(RR) 隔离级别,并通过 MVCC + 间隙锁解决了幻读问题。
Oracle、PostgreSQL 等默认使用读已提交(RC)。
点击每个问题查看详细时序说明
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✗ 可能 | ✗ 可能 | ✗ 可能 |
| READ COMMITTED | ✓ 防止 | ✗ 可能 | ✗ 可能 |
| REPEATABLE READ | ✓ 防止 | ✓ 防止 | ✗ 可能* |
| SERIALIZABLE | ✓ 防止 | ✓ 防止 | ✓ 防止 |
* MySQL InnoDB 的 RR 级别通过 Next-Key Lock(间隙锁)在很大程度上防止了幻读
SQL 标准定义了4个级别,逐级增强
原理:事务可以读取其他事务尚未提交的数据变更。
实现:不加任何读锁,直接读最新版本(最新的 undo log 或 buffer pool 数据)。
问题:脏读、不可重复读、幻读都可能发生。
使用场景:极少使用,仅在能接受脏数据且需要极致性能时考虑(如日志统计、粗略计数)。
原理:只能读取其他事务已提交的数据,杜绝脏读。
实现(MVCC):每次 SELECT 都生成一个新的读视图(Read View),只能看到创建视图前已提交的版本。
问题:两次读取间隔内其他事务提交了修改,导致不可重复读。
使用场景:互联网业务常用,如电商、社交类(对一致性要求不极端严格)。
原理:同一事务内,多次读取同一行结果一致,杜绝不可重复读。
实现(MVCC):事务开始时生成一个固定的 Read View,整个事务内都用这个快照读取。
防幻读:MySQL 额外使用 Next-Key Lock(行锁 + 间隙锁)防止其他事务在范围内插入新行。
使用场景:银行转账、库存扣减等需要强一致性的业务。
原理:所有事务串行执行,完全隔离,等同于单线程。
实现:所有 SELECT 加共享锁(S锁),所有 INSERT/UPDATE/DELETE 加排他锁(X锁),大量锁等待。
优点:完全杜绝所有并发异常。
缺点:性能极差,容易死锁,并发度几乎为0。
使用场景:金融交易核心账务、必须强一致性的场合,实际很少用。
选择隔离级别,观察两个事务并发时的不同表现
选择演示场景:
选择隔离级别:
Multi-Version Concurrency Control — 多版本并发控制
MVCC 是 MySQL InnoDB 实现隔离级别的核心机制。它的核心思想是:读写不互斥 — 读不加锁,写通过版本链隔离,从而大幅提升并发性能。
每行数据背后维护一条版本链(Undo Log Chain),每次修改产生新版本,旧版本保留供事务快照读取。
m_ids:创建时活跃的事务ID列表min_trx_id:活跃事务中最小IDmax_trx_id:下一个将分配的事务IDcreator_trx_id:创建此 Read View 的事务IDSELECT 执行时点击卡片查看详细信息
-- 查看当前隔离级别 SELECT @@transaction_isolation; -- 设置会话级别 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 设置全局级别 SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 或在 my.cnf 中配置 transaction-isolation = REPEATABLE-READ