脏读 幻读 不可重复读 序列化
一、脏读
序号 | 事务A | 事务B |
1 | 开始事务 | |
2 | | 开始事务 |
3 | 修改:A->B | |
4 | | 读取B |
5 | 撤销了修改:B->A | |
6 | 提交事务 | |
结果 | | B |
- 事务B读取到了不存在的数据(B事务读取A事务尚未提交正确的数据)
二、幻读
序号 | 事务A | 事务B |
1 | 开始事务 | |
2 | | 开始事务 |
3 | 数据:A、B | |
4 | | 读取:A、B |
5 | 添加:C、D | |
6 | 提交事务 | |
7 | | 读取:A、B、C、D |
结果 | | 读取:A、B、C、D |
三、不可重复读
序号 | 事务A | 事务B |
1 | | 开始事务 |
2 | 开始事务 | |
3 | | 读取:A |
4 | 修改A—B | |
5 | 提交事务 | 读取:B |
6 | 结果 | 读取:B |
不可重复读和幻读到底有什么区别呢?
- 不可重复读是读取了其他事务更改的数据,针对update操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
- 幻读是读取了其他事务新增的数据,针对insert和delete操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
这时候再理解事务隔离级别就简单多了呢。
事务的隔离级别(四种)
数据库的四个特性:原子性(事务包含的所有数据库操作要么全部成功,要不全部失败回滚)-隔离性(一个事务未提交的业务结果是否对于其它事务可见)-一致性(一个事务执行之前和执行之后都必须处于一致性状态)-持久化(一个事务一旦被提交了,那么对数据库中数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作)
- Read uncommitted 读未提交,事务1读取到事务2未提交的数据,会产生脏读
- Read committed 读提交,避免了脏读,但是可能会造成不可重复读,事务1读取到事务2提交的数据,前后同一条数据不一致,为不可重复读
- Repeatable read 重复读,Mysql的默认隔离级别就是Repeatable read,可以防止幻读(读操作),通过MVCC操作实现,写操作并不能防止。
- Serializable 序列化(串行化)不仅可以避免脏读、不可重复读,还避免了幻读
悲观锁 乐观锁
数据库的锁机制是实现各个隔离级别的基础
- 悲观锁
正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处 于锁定状态。
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机 制,也无法保证外部系统不会修改数据)。
在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。
- 乐观锁
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。
何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如 果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
MVCC(多版本并发控制)
mysql中,默认的事务隔离级别是可重复读(repeatable-read),为了解决不可重复读,innodb采用了MVCC(多版本并发控制)来解决这一问题。
MVCC是利用在每条数据后面加了隐藏的两列(创建版本号和删除版本号),每个事务在开始的时候都会有一个递增的版本号
新增:
insert into user (id,name,age)values(1,"张三",10);

更新:
update user set age = 11 where id = 1;
更新操作采用delete+add的方式来实现,首先将当前数据标志为删除

然后新增一条新的数据:

删除:删除操作是直接将数据的删除版本号更新为当前事务的版本号
delete from user where id = 1;

查询操作:
select * from user where id = 1;
查询操作为了避免查询到旧数据或已经被其他事务更改过的数据,需要满足如下条件:
1、查询时当前事务的版本号需要大于或等于创建版本号
2、查询时当前事务的版本号需要小于删除的版本号
即:create_version <= current_version < delete_version