事务隔离级别
什么是事务隔离级别?
事务隔离级别定义了一个事务在多大程度上可能受到其他并发事务的“干扰”。它是在数据库的数据一致性和并发性能之间进行权衡的核心机制。较低的隔离级别可以提高并发性,但可能导致数据不一致问题;较高的隔离级别可以保证数据的高度一致性,但会牺牲一定的并发性能。
SQL标准(ANSI/ISO SQL-92)定义了四种标准的事务隔离级别,它们主要为了解决以下三种并发问题:
- 脏读:一个事务读到了另一个未提交事务修改的数据。如果那个事务回滚了,那么第一个事务读到的数据就是无效的“脏数据”。
- 不可重复读:在同一个事务中,多次读取同一行数据,结果不一致。这通常是因为在两次读取之间,另一个已提交事务修改或删除了该行数据。
- 幻读:在同一个事务中,多次根据相同条件查询,结果集的行数不一致。这通常是因为在两次查询之间,另一个已提交事务插入或删除了符合条件的数据。
四种标准隔离级别列表介绍
下表清晰地展示了不同隔离级别能解决的问题:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交(read-uncommitted) | ❌ 可能 | ❌ 可能 | ❌ 可能 |
| 读已提交(read-committed) | ✅ 避免 | ❌ 可能 | ❌ 可能 |
| 可重复读(repeatable-read) | ✅ 避免 | ✅ 避免 | ❌ 可能 |
| 可序列化(serializable) | ✅ 避免 | ✅ 避免 | ✅ 避免 |
✅ 避免 | ❌ 可能
下面我们通过一个简单的银行账户场景来详细解释每个级别。 假设有两个事务在同时执行:
- 事务A:从账户1向账户2转账100元。
- 事务B:查询账户1和账户2的总余额。
1. 读未提交
这是最低的隔离级 别,允许一个事务读取另一个事务尚未提交的修改。
- 工作原理:几乎不加锁,或者只加非常短暂的锁。
- 可能发生的问题:
- 脏读:事务A将账户1的余额从1000元改为900元(但尚未提交)。此时事务B读取账户1的余额,得到900元。如果事务A因为某种原因回滚了,账户1的余额恢复为1000元,但事务B已经使用了一个错误的、从未真正存在过的值(900元)。
- 不可重复读和幻读也同样可能发生。
- 使用场景:对数据一致性要求极低,但追求极高并发性的场景,如一些不计精度的统计报告。在实际生产中极少使用。
2. 读已提交
这是许多数据库(如 Oracle, PostgreSQL, SQL Server)的默认隔离级别。它只允许读取已经提交的数据。
- 工作原理:在事务执行过程中,写操作会锁定相关数据行,直到事务结束。读操作通常采用“快照”或“瞬间锁”的方式,保证读到的永远是已提交的最新数据。
- 解决的问题:避免了脏读。
- 可能发生的问题:
- 不可重复读:事务B第一次读取账户1的余额,得到1000元。接着事务A提交了转账,将账户1余额更新为900元。事务B再次读取账户1的余额,得到了900元。在同一个事务B中,两次读取同一数据的结果不一致。
- 幻读:事务B查询余额大于500的账户数量,得到10个。此时事务A插入了一个新的余额为600的账户并提交。事务B再次查询,得到了11个账户。
- 使用场景:大多数应用程序的通用选择,在数据一致性和并发性能之间取得了良好平衡。
3. 可重复读
这是 MySQL InnoDB 存储引擎的默认隔离级别。它确保在同一个事务中,多次读取同一数据的结果是一致的。
- 工作原理:事务开始时创建一个数据快照。在整个事务期间,读操作都基于这个一致性快照,因此不会看到其他已提交事务的修改。写操作则会锁定相关行。
- 解决的问题:避免了脏读和不可重复读。
- 可能发生的问题:
- 幻读:事务B查询余额大于500的账户数量,得到10个。此时事务A插入了一个新的余额为600的账户并提交。由于事务B基于快照读取,它仍然只看到10个账户。但是,如果事务B尝试去更新所有余额大于500的账户,它可能会意外地更新到这个新插入的账户(因为更新操作看不到快照,而是看的当前最新数据),这就产生了“幻读”。
- 注意:MySQL InnoDB 通过“间隙锁”机制在很大程度上防止了幻读,所以在这个引擎中,可重复读级别通常也能避免幻读。但这并非SQL标准的要求。
- 使用场景:需要对同一数据进行多次一致读取的场景,如财务报表生成。
4. 可序列化
这是最高的隔离级别。它强制事务串行执行,相当于单线程操作,完全避免了并发问题。
- 工作原理:通常通过严格的锁机制(如范围锁)来实现,或者使用乐观锁/多版本并发控制(MVCC)的强制冲突检测。它确保不可能发生脏读、不可重复读和幻读。
- 解决的问题:避免了所有并发问题。
- 可能发生的问题:
- 性能严重下降:因为并发事务需要排队执行,系统的吞吐量会急剧下降。
- 超时和死锁的风险大大增加。
- 使用场景:对数据一致性要求极高,且可以接受低并发性能的场景,如银行的核心交易系统、库存管理中防止超卖等。
总结与选择建议
| 隔离级别 | 一致性 | 并发性 | 适用场景 |
|---|---|---|---|
| 读未提交 | 最低 | 最高 | 几乎不用 |
| 读已提交 | 中等 | 高 | 通用场景,大多数数据库的默认选择 |
| 可重复读 | 较高 | 中等 | 需要可重复读的一致性场景(MySQL默认) |
| 可序列化 | 最高 | 最低 | 对数据一致性有极端要求的金融、交易场景 |
如何选择:
- 首选“读已提交”:除非有特殊需求,否则这是一个安全且性能良好的起点。
- 需要“可重复读”:如果你的业务逻辑要求在同一个事务中,多次读取的数据必须绝对一致(例如,先查询后更新,且更新依赖于查询结果)。
- 慎用“可序列化”:仅在绝对必要时使用,因为它对性能的影响是巨大的。通常可以通过在应用层使用悲观锁或乐观锁来替代。
理解事务隔离级别对于设计高并发、高可靠性的应用程序至关重要。