事务隔离级别
1. 先理解什么是“事务”
想象一个场景:你要从你的账户A转账100元到朋友账户B。
这个操作实际上包含几个步骤:
- 检查A账户余额是否 >= 100元。
- 从A账户扣掉100元。
- 向B账户增加100元。
数据库的“事务”就是要把这一系列操作打包成一个不可分割的、整体的操作。这个事务只有两种结果:
- 全部成功:A扣款成功,B收款也成功。
- 全部失败:如果中间任何一步出错(比如A余额不足),那么A的扣款和B的收款都会像没发生过一样。
这就是事务的核心特性:原子性。
2. 问题来了:并发(多个事务一起执行)
现在,银行不止你一个柜员。当你在办理A向B转账的事务时,旁边另一个柜员可能在办理B向C转账的事务,或者经理正在统计所有账户的总金额。
当多个事务同时进行时,如果不加“隔离”,就会产生一些奇怪的问题。主要有三种:
问题一:脏读
- 比喻:你正在给A办理转账,刚扣完A的100元,还没来得及给B加上。这时,经理过来统计总金额,他看到了A账户已经被扣了100元(但B还没收到),于是他记录了一个“错误”的总金额。结果下一秒,你的转账系统出错了,事务回滚,A的100元又恢复了。但经理已经用那个错误的数据做完报表了!
- 本质:读到了另一个未提交事务的中间数据,而这个数据可能最终不会存在。
问题二:不可重复读
- 比喻:你第一次查询A账户的余额,显示是500元。此时,另一个柜员正在办理A账户的取款200元业务并提交了。你还没办完业务,再次查询A账户余额,发现变成了300元!在同一个业务办理过程中,你对同一条数据(A账户余额)的两次读取结果不一致。
- 本质:在同一个事务内,多次读取同一行数据,结果不一样(因为被其他已提交事务修改了)。
问题三:幻读
- 比喻:经理要统计所有余额大于1000元的客户数量,第一次查是10个人。此时,有一个新客户存了一大笔钱开户,他的余额远大于1000元,并且开户成功(事务提交)。经理再次统计,发现变成了11个人!他感觉像出现了幻觉一样。
- 本质:在同一个事务内,多次根据相同条件查询,查询到的记录行数不一致(因为被其他已提交事务新增或删除了数据)。注意它和“不可重复读”的区别:不可重复读是针对已存在行的数据修改,幻读是针对行的数量的变化。
3. 事务隔离级别:解决上述问题的四套规则
为了解决这些问题,数据库设立了4种不同的“工作规则”,也就是4种隔离级别。级别从低到高,解决的问题越多,但代价是性能会越低。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 比喻:柜员的工作模式 |
|---|---|---|---|---|
| 读未提交 | ❌ 可能 | ❌ 可能 | ❌ 可能 | 心直口快模式:你能看到其他柜员正在办但还没确认的业务数据。很不安全,但最快。 |
| 读已提交 | ✅ 避免 | ❌ 可能 | ❌ 可能 | 确认后可见模式:你只能看到其他柜员已经办完确认(提交) 的业务数据。这是大多数数据库的默认级别。 |
| 可重复读 | ✅ 避免 | ✅ 避免 | ❌ 可能 | 快照模式:在你开始办业务时,数据库给你拍了一张快照。在整个业务过程中,你始终只看到这张快照里的数据,不受外界影响。 |
| 串行化 | ✅ 避免 | ✅ 避免 | ✅ 避免 | 单人窗口模式:数据库排队,一次只处理一个事务。绝对安全,但速度最慢。 |
4. 详细解释与场景
1. 读未提交
- 何时使用:几乎不用,除非你对数据一致性要求极低,且追求极致性能。
2. 读已提交
- 这是最常用的级别。比如在网购时,你查询商品库存,看到还剩1件。在你下单的瞬间,可能被别人买走了,导致你下单失败。这你能接受,因为你看到的是“已确认”的实时数据。
- 解决了脏读,但可能遇到不可重复读和幻读。
3. 可重复读
- MySQL的默认级别。非常实用。
- 比如你要对一笔账进行复杂的、多次的核查。数据库在核查开始时给你创建了一个“数据快照”,在整个核查过程中,无论外面的数据怎么变,你每次查到的都是快照里的数据,保证了核查过程的一致性。
- 解决了脏读和不可重复读。在MySQL中,通过一种叫“间隙锁”的机制,很大程度上也避免了幻读。
4. 串行化
- :数据库排队,一次只处理一个事务。绝对安全
- 何时使用:在对数据准确性要求极高,且不计较性能的场景下,比如银行的核心结算系统。
总结
事务隔离级别就是在“数据准确性”和“系统性能”之间做权衡。
- 级别越低,并发性越好,但数据越可能出错。
- 级别越高,数据越安全,但并发性越差,性能越低。
- 有脏读、不可重复读、幻读这三种并发问题。
- 数据库用读未提交、读已提交、可重复读、串行化 这四种级别来解决它们。
- 最常用的是读已提交,MySQL默认是可重复读。
