目录
[1. 事务相关概念回顾](#1. 事务相关概念回顾)
[2. 事务隔离级别与问题对应关系](#2. 事务隔离级别与问题对应关系)
[1. 准备表与数据](#1. 准备表与数据)
[2. 查看/设置全局隔离级别](#2. 查看/设置全局隔离级别)
[3. 脏读(Read Uncommitted)](#3. 脏读(Read Uncommitted))
[4. 读已提交(Read Committed)](#4. 读已提交(Read Committed))
[5. 不可重复读(Repeatable Read)](#5. 不可重复读(Repeatable Read))
[6. 解决不可重复读(调整隔离级别为可重复读)](#6. 解决不可重复读(调整隔离级别为可重复读))
[7. 幻读(Phantom Read)](#7. 幻读(Phantom Read))
[8. 串行化(Serializable)](#8. 串行化(Serializable))
一、上节课复习
1. 事务相关概念回顾
-
事务是什么
-
事务的基本特性(ACID)
-
事务的使用
-
保存点(~~)
-
事务的隔离级别(重点&难点)
2. 事务隔离级别与问题对应关系
| 隔离级别 | 可能出现的问题 |
|---|---|
| 读未提交 | 脏读、不可重复读、幻读 |
| 读已提交 | 不可重复读、幻读 |
| 可重复读 | 幻读 |
| 串行化 | 无(但性能低) |
上节课操作过程中,修改的隔离级别,看起来没有生效~~
和 navicat 有关~~ 修改的隔离级别,需要把 navicat 的连接断开,重新连接,才能生效。
(已经和 mysql 服务器建立好的连接,没有受到影响)
二、视图(~~)
针对查询语句,封装~~
三、隔离级别示例(结合操作步骤+截图)
1. 准备表与数据
sql
use java117_2;
show tables;
select * from bank_account;
delete from bank_account;
desc bank_account; -- 自己创建一个表(表结构:id int(PK)、name varchar(20)、balance int)
insert into bank_account values(null, '张三', 1000), (null, '李四', 2000), (null, '王五', 3000);
2. 查看/设置全局隔离级别
sql
SELECT @@GLOBAL.transaction_isolation;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
3. 脏读(Read Uncommitted)
客户端1操作:
sql
start TRANSACTION;
update bank_account set balance = 10000 where name = '张三';
select * from bank_account;
-- 不去提交数据,看另一个客户端的事务是否能够读取到。
-- commit; (先不提交)
客户端2操作:
sql
SELECT @@GLOBAL.transaction_isolation;
use java117_2;
start TRANSACTION;
select * from bank_account; -- 此时能读到客户端1未提交的10000(脏读)
commit;
虽然第一个客户端没有 commit,但是第二个客户端也读取到了 10000 这个结果,此时就是脏读~~
4. 读已提交(Read Committed)
提升隔离级别到 READ COMMITTED后,客户端2再次测试:
客户端1操作:
sql
start TRANSACTION;
update bank_account set balance = 10000 where name = '张三';
select * from bank_account;
-- 不提交,看客户端2读取结果
客户端2操作:
sql
SELECT @@GLOBAL.transaction_isolation; -- 确认是READ COMMITTED
use java117_2;
start TRANSACTION;
select * from bank_account; -- 此时读不到10000(因为客户端1未提交)
commit;
提升隔离级别到
READ COMMITTED,此时读到的数据是第一个客户端执行事务之前的数据~~
5. 不可重复读(Repeatable Read)
步骤1:客户端2开启事务并第一次查询
sql
start TRANSACTION;
select * from bank_account; -- 第一次读,张三余额1000
select * FROM bank_account; -- 再读一次,还是1000
commit;
步骤2:客户端1修改数据并提交
sql
start TRANSACTION;
update bank_account set balance = 10000 where name = '张三';
select * from bank_account; -- 看到10000
commit; -- 提交事务
步骤3:客户端2在同一事务内再次查询
sql
-- 第三步,在同一个事务内部,再次读取~~
select * FROM bank_account; -- 第二次读,张三余额变成10000(不可重复读)
commit;
客户端2中,同一个事务之内,两次读取到的结果不相同 ,这就是不可重复读。
6. 解决不可重复读(调整隔离级别为可重复读)
把隔离级别调整为 REPEATABLE READ后,重复上述操作:
客户端2操作:
sql
start TRANSACTION;
select * from bank_account; -- 第一次读,张三1000
select * FROM bank_account; -- 第二次读,还是1000(不可重复读问题解决)
commit;
相同的操作步骤下,第二个客户端,第二次读取结果仍然是 1000,不可重复读问题就解决了~~
7. 幻读(Phantom Read)
步骤1:客户端2开启事务并第一次查询
sql
start TRANSACTION;
select * from bank_account; -- 第一次读,无赵六
步骤2:客户端1插入新数据并提交
sql
start TRANSACTION;
insert into bank_account values(null, '赵六', 1000); -- 插入新行
select * from bank_account; -- 看到赵六
commit;
步骤3:客户端2在同一事务内再次查询
sql
select * FROM bank_account; -- 这次查询结果中,没有包含赵六(未出现幻读)
可重复读隔离级别,本身已经处理掉大部分的幻读情况了~~
8. 串行化(Serializable)
把隔离级别提升到 SERIALIZABLE后,测试幻读:
客户端1操作:
sql
start TRANSACTION;
insert into bank_account values(20, '赵六', 1000); -- 插入
select * from bank_account;
commit;
客户端2操作:
sql
start TRANSACTION;
select * from bank_account; -- 第一步查询
-- 第二步,启动一个事务,插入相同id的数据(测试幻读/阻塞)
insert into bank_account values(20, '赵六', 1000); -- 插入失败(主键冲突)
select * FROM bank_account;
commit;
一个事务没有执行完,第二个事务只能等~~ 过一段时间之后,就会失败~~
客户端1插入失败,因为20主键已经存在(
Duplicate entry '20' for key 'PRIMARY')
正常来说,是会直接告诉我们"影响了一行",但串行化下,事务会排队执行,避免并发问题。
四、总结(结合所有逻辑)
-
脏读:读未提交时,读取到其他事务未提交的数据。
-
不可重复读:读已提交时,同一事务内两次读取结果不同(其他事务修改了数据)。
-
幻读:同一事务内,两次查询++结果集行数++不同(其他事务插入/删除数据)。
-
隔离级别从低到高:
READ UNCOMMITTED→READ COMMITTED→REPEATABLE READ→SERIALIZABLE,隔离级别越高,并发性能越低,但数据一致性越强。