四种隔离级别,它们分别在不同的程度上为并发操作的正确调度提供一定的保证,本次实验的目的就是为了验证各种隔离级别的并发控制效果
(1)读未提交(Read Uncommitted);
(2)读提交(Read Committed, RC);
(3)可重复读(Repeated Read, RR);mysql 默认使用级别。
(4)串行化(Serializable);
脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。
对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
可重复读
可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。
幻读是针对数据插入(INSERT)操作来说的,当同一个查询在不同时间生成不同的行集合时就是出现了幻读。本质上是由于数据插入(INSERT)操作,使得已提交事务B对事务A产生了数据不一致的影响。
【例】【假设事务 A 对某些行的内容作了更改,但是还未提交,此时事务 B 插入了与事务 A 更改前的记录相同的记录行,并且在事务 A 提交之前先提交了,而这时,在事务 A 中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务 B 刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。】
1、创建info表,并初始化。
2、启动两个不同线程的客户端,分别命名为A和B
show processlist ;#查看连接线程
3、取消MySQL的自动提交功能,设置完不自动提交后,通过begin开启事务,写完SQL语句,需要进行commit或者rollback处理
set autocommit = 0;
begin;
commit; or rollback
或者采用事务的方式,输入“start transaction”来开始 事务,最后进行commit或者rollback。
start transaction;
commit; or rollback
为了方便接下来的问题阐述,左右命令行分别代表A和B客户端,其中开启的事务称为A事务和B事务
设置隔离级别并查看,将A、B线程的隔离级别均设置为读未提交。经验证,当前隔离级别无法解决脏读、不可重复读、幻读,验证过程见下文。
set @@session.tx_isolation='READ-UNCOMMITTED'; #设置当前线程隔离级别
show variables like 'tx_isolation'; #查看隔离级别
如下图,A事务对money字段由36修改为18时,但是未提交。B事务读取的money字段为18,为A事务未提交的数据。即读到了并不一定最终存在的数据,就是脏读。本次实验中,A事务最终回滚,导致B读取到的数据与数据库中的真实数据36不一致。
由下图,由于收到B事务对数据的修改操作,A事务连续两次对数据的读取出现了不一致现象,说明对于当前的隔离级别,无法避免不可重复读问题。
在A事务中查询info中所有数据,共三条,然后在B事务中插入一条数据,此时,在 A 中按照相同条件查询,查询到的结果多了一行,产生了幻读现象。因此在当前隔离级别下,无法避免幻读问题。
设置隔离级别并查看,将A、B线程的隔离级别均设置为读已提交(Read Committed)。
set @@session.tx_isolation='READ-COMMITTED';
show variables like 'tx_isolation';
经验证,当前隔离级别可以解决脏读,但无法避免不可重复读、幻读,验证过程见下文。
如下图,当事务A对数据进行修改后,用B事务读取数据,由于此时事务A还没有提交,事务B读取到的数据仍未32,说明当前隔离级别的要求决定了事务B只能读取已经提交的之后的数据,因此可以保证B事务读到的数据一定是最终存在的数据,即有效避免了脏读的问题。
A事务对数据查询,money字段为16,当B事务对数据进行修改,并提交以后,再通过A事务进行查询,此时数据的值变为32,连续两次的数据读取,产生了不一致现象,因此没有避免不可重复读。
在A事务中查询info中所有数据,共6条,然后在B事务中新增一条数据,提交B事务。(注意,如果不提交B事务,则A中查询结果无变化,因此此时已经解决了脏读问题。)此时,在 A 中按照相同条件查询,查询到的结果多了一行,产生了幻读现象。因此在当前隔离级别下,无法避免幻读问题。
设置隔离级别并查看,将A、B线程的隔离级别均设置为可重复读(Repeated Read)。
set @@session.tx_isolation='REPEATABLE-READ';
show variables like 'tx_isolation';
经验证,当前隔离级别可以解决脏读、不可重复读,但无法避免幻读,验证过程见下文。
采用的实验方案和上述隔离级别一致,产生的实验结果也一致,实验证明可以有效到的避免读“脏”数据的现象发生。
在A中查询id=1记录的money字段,为60,在B中更新money字段,开始时为60,更新为money = money / 2 = 30,此时在A中再次查询,仍为60。
提交B事务之后,再次查询,依然为60。
说明在一个事务运行期间,即便其他事务对数据进行修改,前后两次读取到的数据保持一致,因此可以避免“不可重复读”。
此时提交A事务以后,再次查询id=1记录的money字段,结果才显示为30(更新之后的值)
实验过程中,在A事务中查询info中所有数据,共2条,然后在B事务中插入一条数据,并提交事务。此时,在 A 中按照相同条件查询,查询到的结果仍然为2条,没有产生幻读现象。当A提交事务时,查询到了新增的记录。
**MySQL 的可重复读隔离级别通过间隙锁解决了部分幻读问题,但插入相同主键会冲突。**例如,B事务插入了ID=3的记录并提交,此时A事务中查询不到新增记录,但是,却无法插入相同主键(ID)的记录,这也属于B事务对A事务产生的不一致影响,也属于幻读现象。
设置隔离级别并查看,将A、B线程的隔离级别均设置为串行化(Serializable)。
set @@session.tx_isolation='SERIALIZABLE'; #设置当前线程隔离级别,GLOBAL全局
show variables like 'tx_isolation'; #查看隔离级别
或者设置全局隔离级别:
set global transaction isolation level SERIALIZABLE;
经验证,当前隔离级别可以解决脏读、不可重复读、幻读,验证过程见下文。
采用的实验方案和上述隔离级别一致,产生的实验结果也一致,实验证明可以有效到的避免脏读的现象发生。
采用的实验方案和上述隔离级别一致,产生的实验结果也一致,实验证明可以有效到的避免不可重复读的现象发生。
A中查询到四条记录,此时通过B,发出INSERT的请求,请求处于阻塞状态。A事务提交以后,B中的插入请求被执行。事务的提交本质上变为串行的方式,有效避免了幻读现象。当B事务提交以后,A中查询到的记录变为5条。
SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:
读未提交(READ UNCOMMITTED)
读提交 (READ COMMITTED)
可重复读 (REPEATABLE READ)
串行化 (SERIALIZABLE)
从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。
事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读 | 可能 | 可能 | 可能 |
已提交读 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
串行化读 | 不可能 | 不可能 | 不可能 |