事务的隔离级别(四种)
数据库的四个特性:原子性(事务包含的所有数据库操作要么全部成功,要不全部失败回滚)隔离性(一个事务未提交的业务结果是否对于其它事务可见)-一致性(一个事务执行之前和执行之后都必须处于一致性状态)-持久化(一个事务一旦被提交了,那么对数据库中数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作)
- Read uncommitted 读未提交,事务1读取到事务2未提交的数据,会产生脏读
- Read committed 读提交,避免了脏读,但是可能会造成不可重复读,事务1读取到事务2提交的数据,前后同一条数据不一致,为不可重复读
- Repeatable read 重复读,Mysql的默认隔离级别就是Repeatable read,可以防止幻读(读操作),通过MVCC操作实现,写操作并不能防止。
- Serializable 序列化(串行化)不仅可以避免脏读、不可重复读,还避免了幻读
- 环境准备,先将当前查询的窗口1和窗口2的隔离级别设置为未提交读
- 窗口1
- 窗口2
- 窗口1
- 准备好数据库表和数据
- 开始演示
一、脏读
读取到其他事务未提交的数据即为脏读
| 事务A | 事务B |
|---|---|
开始事务 START TRANSACTION; | |
开始事务 START TRANSACTION; | |
修改:UPDATE user_account SET balance = balance + 100 WHERE account_id = 1; | |
读取 SELECT * FROM test.user_account WHERE account_id =1; | |
撤销了修改:rollback | |
| 结果 | 事务B读取到了不存在的数据(B事务读取A事务尚未提交正确的数据) |
二、不可重复读
读取同一条数据的两次的结果不一样
- 先准备环境,将当前窗口的隔离级别改为读已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
| 事务A | 事务B |
|---|---|
开始事务 start transaction; | |
开始事务 start transaction; | |
读取:SELECT * FROM test.user_account WHERE account_id =1; | |
修改 UPDATE user_account SET balance = balance + 100 WHERE account_id = 1; | |
| 提交事务 | |
再次读取:SELECT * FROM test.user_account WHERE account_id =1; | |
| 结果 | 事务B读取同一条数据的两次的结果不一样 |
三、幻读
准备环境,由于Mysql REPEATABLE READ可重复读级别下通过手段避免了幻读的情况,但是不要忘记了,幻读是数据库标准下可重复读这个级别会产生的问题,为了演示,所以当前隔离级别还是RC——将当前窗口的隔离级别改为读已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
| 事务A | 事务B | |
|---|---|---|
开始事务start transaction; | ||
开始事务start transaction; | ||
数据:select * from test.user_account; | ||
数据:select * from test.user_account; | ||
| 添加:insert into test.user_account values(4,5000,'liu'); | ||
提交事务 commit | ||
更新数据:UPDATE user_account SET balance = balance + 100 ; | ||
| 结果 | 事务B更新了四条,跟之前预想的三条不一样 |
不可重复读和幻读到底有什么区别呢?
- 不可重复读是读取了其他事务更改的数据,针对update操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
- 幻读是读取了其他事务新增的数据,针对insert和delete操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
这时候再理解事务隔离级别就简单多了呢。
InnoDB可重复读隔离级别下是如何避免幻读的
在刚刚的实验中我们知道,Mysql innodb下可重复读是可以避免幻读的,那它是咋实现的呢
- 表象:快照读--伪MVCC
- 内在:NEXT-KEY锁(行锁+GAP锁)
当前读和快照读
- 当前读
select ...lock in share mode,select ...for update
update,delet,insert
- 快照读
select + 不加锁的非阻塞读
悲观锁 乐观锁
数据库的锁机制是实现各个隔离级别的基础
- 悲观锁 正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处 于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机 制,也无法保证外部系统不会修改数据)。 在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。
- 乐观锁 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( 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