数据库基础——事务隔离级别
数据库基础——事务隔离级别
事务隔离级别是数据库系统中非常重要的概念,它决定了事务之间如何相互"看到"对方的数据变化。我将用更通俗易懂的方式解释这四种隔离级别,特别是你感到困惑的"读已提交"和"可重复读"级别。
一、四种隔离级别概述
我们可以把隔离级别想象成教室之间的隔墙厚度:
隔离级别 | 隔墙厚度 | 问题解决 | 存在问题 |
---|---|---|---|
读未提交 | 透明玻璃 | 无 | 脏读 |
读已提交 | 薄墙 | 解决脏读 | 不可重复读 |
可重复读 | 厚墙 | 解决脏读、不可重复读 | 幻读 |
串行化 | 实心墙 | 解决所有问题 | 性能差 |
二、各级别详细解释
1. 读未提交(Read Uncommitted) - 透明玻璃
特点:事务可以读取其他事务未提交的修改。
问题:
- 脏读(Dirty Read):读到别人还没确认的数据,如果对方回滚,你读到的就是"脏数据"。
示例:
-- 事务A
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 未提交-- 事务B(读未提交隔离级别)
SELECT balance FROM accounts WHERE id = 1; -- 能看到A未提交的修改
2. 读已提交(Read Committed) - 薄墙
特点:只能读取已提交的数据。
解决了:脏读问题。
新问题:
- 不可重复读(Non-repeatable Read):在同一事务中,两次读取同一数据可能结果不同。
示例:
-- 事务A第一次查询
SELECT balance FROM accounts WHERE id = 1; -- 返回1000-- 事务B提交了修改
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT;-- 事务A第二次查询(同一事务内)
SELECT balance FROM accounts WHERE id = 1; -- 返回900(与第一次不同)
现实比喻:你在看一本书,别人可以在你阅读过程中修改书的内容,你每次翻页可能看到不同的内容。
3. 可重复读(Repeatable Read) - 厚墙
特点:事务开始时建立"快照",整个事务期间都读取这个快照。
解决了:脏读和不可重复读问题。
新问题:
- 幻读(Phantom Read):虽然同一数据不会变,但可能出现新数据行。
示例:
-- 事务A开始
SELECT COUNT(*) FROM accounts WHERE type = 'saving'; -- 返回5-- 事务B插入并提交
INSERT INTO accounts(id, type, balance) VALUES(11, 'saving', 500);
COMMIT;-- 事务A再次查询
SELECT COUNT(*) FROM accounts WHERE type = 'saving'; -- 仍返回5(可重复读)-- 但事务A执行更新时会发现影响行数超出预期
UPDATE accounts SET balance = balance + 10 WHERE type = 'saving';
-- 可能影响6行而不是预期的5行
现实比喻:你在看一本书的拍照快照,书的内容实际可能已经变化,但你看到的始终是拍照时的内容。不过如果有人往书里夹新页,你可能会发现。
4. 串行化(Serializable) - 实心墙
特点:完全隔离,事务串行执行。
解决了:所有问题(脏读、不可重复读、幻读)。
代价:性能最低,并发性差。
三、重点对比:读已提交 vs 可重复读
读已提交(Read Committed)
- 数据真实性:总是读取最新已提交的数据
- 问题:同一事务内多次读取同一数据可能结果不同
- 适用场景:需要实时数据的系统(如银行ATM机查询余额)
可重复读(Repeatable Read)
- 数据一致性:事务开始时建立数据快照,整个事务期间读取相同数据
- 问题:可能读到"过期"数据,存在幻读
- 适用场景:需要数据一致性的报表生成、统计分析
四、如何选择隔离级别
- 优先考虑可重复读:MySQL默认级别,平衡了性能和数据一致性
- 需要高实时性:考虑读已提交(Oracle默认)
- 严格要求数据一致性:考虑串行化(但性能影响大)
- 几乎不使用:读未提交(仅用于特殊场景)
五、实际案例分析
银行转账场景
-- 使用可重复读隔离级别
START TRANSACTION;-- 检查账户A余额(假设返回1000)
SELECT balance FROM accounts WHERE id = 1;-- 在此期间,另一个事务从账户A转出100并提交
-- 但当前事务仍看到余额为1000-- 尝试转出800
UPDATE accounts SET balance = balance - 800 WHERE id = 1;
-- 实际会检查真实余额,如果不足会失败,防止透支