理解SQL事务隔离等级以及各种读写异常

背景

目前,主流 db 都支持事务。但事务保证完全 ACID 是有代价的。

所以有时候会为了性能,在某些场景种,弱化事务的 ACID。这是通过设置不同的隔离级别来实现的。

不同的隔离级别能解决不同的读写异常场景,要了解这些读写异常场景,才能根据情况选择合适的隔离级别。

数据隔离等级

快照隔离

快照隔离的一个事务读到的数据都来自于数据库某同一个时刻的状态(“快照”得名于此),然后所有写都发生在之后的某同一个时刻。

快照隔离案例:MongoDB

MongoDB 在开始事务的时候,不会生成快照。在第一次操作(读 or 写)才会触发快照生成。

根据快照隔离的含义,可能会出现更新丢失、写偏的情况

可序列化

可序列化的每个事务都是完全独立的,一个事务完成后才会做下一个。

最高级别的隔离保证,规避任何问题的出现。

读写异常

脏读(读取未提交的数据)

如果一个事务 A 向数据库写了数据,但事务还没提交或终止,另一个事务 B 就看到了事务 A 写进数据库的数据,这就是脏读。

读偏 / 不可重复读(前后多次读取,数据内容不一致)

事务 A 读取数据,此时为 20;事务 B 执行数据更新,变为 30 并且提交;事务 A 还没结束,需要再次读取数据,此时为 30。

对比脏读:脏读是读取另一个事务未提交事务,不可重复读是在一次事务中,读到两种数据状态。

幻读(前后多次读取,数据总量不一致)

一个事务的写入改变了另一个事务的查询结果的正确性,这就是幻读。

例如:第一个事务读取一个结果集后,第二个事务,对这个结果集经行增删操作并提交;然而第一个事务中再次对这个结果集进行查询时,数据发现丢失或新增。

脏写

当两个事务同时尝试去更新某一条数据记录时,就肯定会存在一个先一个后。而当事务 A 更新时,事务 A 还没提交,事务 B 就也过来进行更新,覆盖了事务 A 提交的更新数据,这就是脏写。

更新丢失

两个事务并发写入的时候,其中一个事务的修改可能丢失,因为写入的内容没有包括第一个事务的修改。

例如:事务 A 和 B 同时读取到数据 a,将其更新为 b,同时提交,那么 A 和 B 只有一个会成功。

写偏

假设公司里可以同时有多位员工值班,但至少有一位员工在值班。员工可以申请不值班,只要至少有一个同事正在值班,申请即可通过。

此时,小明和小红是两位值班员工。他们都决定请假,恰好在同一时间点击按钮下班。

创建两个事务,应用首先检查是否有两个或以上的员工正在值班。两个事务的读取都返回当前值班员工数量为 2。所以小明/小红都成功更新自己的记录休班了,两个事务更新成功。

但是结果是不满足最初设定的条件:至少有一位员工在值班。

参考链接