关于脏读,不可重复读和幻读
关于脏读,不可重复读和幻读
0.导言
脏读,不可重复读和幻读都是对于事务来说的,可以看成事务是一个SQL操作的最小操作单元。按照对事务的定义,必须满足ACID,即:
- 原子性(Atomicity)
- 即一个事务的所有操作,要么全部成功,要么全部失败,失败后会回滚至事务开始的时候
- 一致性(Consistency)
- 要求事务不能破坏数据库的结构,意为数据必须符合所有预设规则,精确
- 隔离性(Isolation)
- 要求各个事务之间不能互相影响造成数据误差,这是我们着重需要解决的方面
- 持久性(Durability)
- 要求对数据修改是永久的,可以持久化
1.隔离级别
隔离级别如下,×代表无法预防
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | × | × | × |
读已提交(READ COMMITTED) | √ | × | × |
可重复读(REPEATABLE READ) | √ | √ | × |
串行化(SERIALIZABLE) | √ | √ | √ |
1.读未提交(READ UNCOMMITTED)
读未提交,字面意为任何事务都能读改其他还没有提交事务的数据
这就完全有可能干扰事务得出正确结果
这个相当于什么保障都没有,纯纯裸奔.
脏读,不可重复读,幻读都会发生
2.读已提交(READ COMMITTED)
同理,意为只能事务只能读改已经提交了的事务的数据
能避免脏读,却无法避免不可重复读以及幻读
3.可重复读(REPEATABLE READ)
这是MySQL的默认策略,本质是通过MVCC以及一堆锁来控制的,属于乐观锁,不会对性能造成很大影响
简单来说,就是通过在开始事务时创建一个带有时间戳性质的事务id以及回滚id,事务只能操作到自己那个时间戳的事务数据,进而实现了事务数据的隔离。
但是仍然有可能发生幻读。
4.串行化(SERIALIZABLE)
本质是一个悲观锁,把并行变成了串行
这样的确解决了所有问题,毕竟所有问题都是来自并发产生的
但效率可想而知的低
2.并发的问题
1.脏读
脏读,见名思义,会弄脏数据,比如:
假设a = 1
事务A | 事务B |
---|---|
UPDATE a = 114514(未提交) | |
GET a | |
出现错误,回滚 | |
事务结束 | 事务结束 |
这里事务A照理来说应该会返回1,但是A读到了错误数据,没有像事务B变回来,还是114514
就是因为读取了未提交的数据
隔离等级为读未提交(READ UNCOMMITTED)
这就是脏读
事务B把事务A的数据弄脏了
2.不可重复读
不可重复读的意思为,在一个事务中,读同一个值的结果不一样
假设a = 1
事务A | 事务B |
---|---|
UPDATE a = 114514(未提交) | |
GET a,返回1 | |
提交 | |
GET a,返回114514 |
这里貌似体现不出来不可重复读的危害,那在这里举一个例子:
现在银行在一个事务中抽出存了一个亿到两个亿和两个亿以上的人抽奖,假设wyh存了一个亿,第一次固然把他选上了,
但在这个事务要去查询两个亿以上的人时,wyh又存了一个亿进去
那由于不可重复读,又选上了wyh,但是照理来说wyh只能拿到一亿到两亿的那一次抽奖!
所以不可重复读也很重要!
MySQL的Innodb引擎通过MVCC(快照读)控制的,属于乐观锁,不会对性能造成很大影响
简单来说,就是通过在开始事务时创建一个带有时间戳性质的事务id以及回滚id,事务只能操作到自己那个时间戳对应的事务数据,进而实现了事务数据的隔离,这里这是抽象,但实际操作要讲究得多
Innodb引擎的另外一个实现方式是采用当前读
采用了next-key lock,一大堆锁来实现,性能略有影响
这样对于银行的抽奖系统,数据就停留在wyh只有一亿的情况,完美解决
3.幻读
幻读和不可重复读有着相似之处,幻读带个幻,意为遇到了幻影数据
举个例子,假设数据库里面已经有了两条数据
事务A | 事务B |
---|---|
(事务B的数据快照只有两条数据,主键到2) | |
INSERT a = 1(未提交,主键为3) | |
提交,数据库中有三条数据,主键到3 | |
(事务B的数据快照还是只有两条数据,主键到2) | |
INSERT a = 1(未提交,主键为3) | |
提交失败,主键3已经存在,冲突**(幻影数据)** |
幻读与不可重复读区别就在于此,幻读会 操作 一个 冲突的数据 而报错
而不可重复读只有读
对于MySQL的Innodb引擎来说,采用MVCC(快照读)避免了单纯读的幻读问题(实际就是不可重复读)
但是单纯采用MVCC并不能避免幻读
对于Innodb的另一种模式**,当前读**也没能完全的解决幻读
只能说很大程度上避免了幻读问题
这里只是简单的阐述一下,当前读的原理以及为什么不能完全避免幻读我后面会单独再写一篇文章讲讲…