MySQL基础关键_012_事务
目 录
一、概述
二、ACID 四大特性
三、MySQL 事务
四、事务隔离级别
1.说明
2.现象
(1)脏读
(2)不可重复读
(3)幻读
3.查看隔离级别
4.设置隔离级别
5.隔离级别
(1)初始化
(2)读未提交
(3)读提交
(4)可重复读
(5)串行化
6.可重复读的幻读
(1)说明
(2)快照读解决幻读
(3)当前读解决幻读
(4)出现幻读的两种情况
一、概述
- 事务是一个最小的工作单元。数据库中,事务表示一个完整的行为;
- 事务只针对 DML 语句;
- 使用事务机制后,在同一个事务当中,多条 DML 语句或同时执行成功,或同时执行失败;
- 事务的经典实例:账户转账,或一方金额减少和一方金额增加同时成功,或同时失败。
二、ACID 四大特性
- 原子性(Atomicity):事务包含的所有操作或全部成功,或全部失败;
- 一致性(Consistency):事务开始前和完成后,数据应该是一致的;
- 隔离性(Isolation):多个用户并发访问数据库时,数据库为每一个用户开启的事务不能被其他事务的操作所干扰,多个并发事务之间要相互隔离;
- 持久性(Durability):一个事务一旦被提交,则对数据库中的数据改变是永久的,即便是数据库系统出现故障也不会丢失提交事务的操作。
三、MySQL 事务
- 开启 MySQL 事务:【start transaction;】或【begin;】;
- 回滚事务:【rollback;】;
- 提交事务:【commit;】;
- 只要执行 rollback 或 commit,事务都会结束;
- MySQL 默认事务自动提交,执行一条 DML 语句提交一次。
四、事务隔离级别
1.说明
隔离级别 | 现 象 | ||
---|---|---|---|
脏读 | 不可重复读 | 幻读 | |
读未提交 (read uncommitted) | 存在 | 存在 | 存在 |
读提交 (read committed) | 不存在 | 存在 | 存在 |
可重复读 (repeatable read) | 不存在 | 不存在 | 存在 |
串行化 (serializable) | 不存在 | 不存在 | 不存在 |
- 隔离级别排序:读未提交 < 读提交 < 可重复读 < 串行化;
- 不同隔离级别会存在不同现象,按照严重性排序:脏读 > 不可重复读 > 幻读;
- Oracle 默认隔离级别是读提交,MySQL 默认隔离级别是可重复读。
2.现象
(1)脏读
一个事务读取了另一个事务尚未提交的数据。此时如果另一个事务回滚或修改了数据,那么读取脏数据的事务就会出现数据处理错误。
(2)不可重复读
一个事务内,多次读取同一条数据,得到的结果可能不一致。因为其他事务可能对该数据做出修改操作。
(3)幻读
事务执行过程中,前后几次相同查询条件得到的结果集不一致,可能更多也可能更少。
3.查看隔离级别
- 查看当前会话的隔离级别:【select @@transaction_isolation;】;
- 查看全局的隔离级别:【select @@global.transaction_isolation;】。
select @@global.transaction_isolation;select @@transaction_isolation;
4.设置隔离级别
- 设置当前会话的隔离级别:【set session transaction isolation level <隔离级别>】;
- 设置全局的隔离级别:【set global transaction isolation level <隔离级别>】。
# 设置全局隔离级别-- 读未提交
set global transaction isolation leve read uncommitted;-- 读提交
set global transaction isolation level read committed;-- 可重复读
set global transaction isolation level repeatable read;-- 串行化
set global transaction isolation level serializable;# 设置会话隔离级别-- 读未提交
set session transaction isolation leve read uncommitted;-- 读提交
set session transaction isolation level read committed;-- 可重复读
set session transaction isolation level repeatable read;-- 串行化
set session transaction isolation level serializable;
5.隔离级别
(1)初始化
事先准备好一个 users 表,表中有 id,name,gender 字段。
drop table if exists users;create table users(id int primary key auto_increment,name varchar(10),gender varchar(2) default '未知'
);insert into users(name, gender) values('黄梓婷', '女'),('赵聪', '男'),('吕不韦', '男');select * from users;
(2)读未提交
- A 事务与 B 事务,A 事务可以读到 B 事务未提交的数据;
- 这种级别的两个事务之间几乎没有隔离,实际数据库产品中,没有默认该隔离级别的;
- 但前隔离级别,脏读、不可重复读、幻读 现象都存在;
- 模拟实例:先将全局事务隔离级别设置为 read uncommitted。然后开启两个 dos 命令窗口分别登录 MySQL 来模拟 A、B事务。
-- 1.设置全局隔离级别
set global transaction isolation level read uncommitted;-- 2.然后打开两个 dos 命令窗口,分别登录 MySQL
A 事务 | B 事务 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8. mysql> insert into users(name, gender) values('陈子悦', '女'); | |
9. mysql> select * from users; |
可以看到,出现了脏读,意味着三个现象都会出现。
(3)读提交
- A 事务与 B 事务,A 事务可以读取到 B 事务提交之后的数据;
- Oracle 默认是此隔离级别;
- 当前隔离级别,会出现不可重复读和幻读现象;
- 模拟实例:先将全局事务隔离级别设置为 read committed。然后开启两个 dos 命令窗口分别登录 MySQL 来模拟 A、B事务。
-- 1.设置全局隔离级别
set global transaction isolation level read committed;-- 2.然后打开两个 dos 命令窗口,分别登录 MySQL
A 事务 | B 事务 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8. mysql> insert into users(name, gender) values('陈子悦', '女'); | |
9. mysql> select * from users; | |
9. mysql> commit; | |
10. mysql> select * from users; | |
11. mysql> update users set name = '牛佳佳' where id = 2; | |
12. mysql> select * from users; |
可以看到,出现了不可重复读和幻读。
(4)可重复读
- A 事务与 B 事务,A 事务开启后读取记录,B 事务修改数据并提交,A 事务读取到的还是修改前的记录;
- MySQL 默认是此隔离级别;
- 模拟实例:先将全局事务隔离级别设置为 repeatable read。然后开启两个 dos 命令窗口分别登录 MySQL 来模拟 A、B事务。
-- 1.设置全局隔离级别
set global transaction isolation level repeatable read;-- 2.然后打开两个 dos 命令窗口,分别登录 MySQL
A 事务 | B 事务 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8. mysql> update users set name = '艾东东' where id = 2; | |
9. mysql> select * from users; | |
10. mysql> commit; | |
11. mysql> select * from users; | |
12. mysql> insert into users(name, gender) values('张弘毅', '男'); | |
13. mysql> commit; | |
14. mysql> select * from users; | |
15. select * from users for update; |
# A 事务
mysql> use test;
Database changed
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黄梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 吕不韦 | 男 |
| 5 | 陈子悦 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黄梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 吕不韦 | 男 |
| 5 | 陈子悦 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黄梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 吕不韦 | 男 |
| 5 | 陈子悦 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黄梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 吕不韦 | 男 |
| 5 | 陈子悦 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users for update;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黄梓婷 | 女 |
| 2 | 艾东东 | 男 |
| 3 | 吕不韦 | 男 |
| 5 | 陈子悦 | 女 |
| 6 | 张弘毅 | 男 |
+----+--------+--------+
5 rows in set (0.00 sec)
# B 事务
mysql> use test;
Database changed
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> update users set name = '艾东东' where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0mysql> commit;
Query OK, 0 rows affected (0.00 sec)mysql> insert into users(name, gender) values('张弘毅', '男');
Query OK, 1 row affected (0.02 sec)mysql> commit;
Query OK, 0 rows affected (0.00 sec)
可以看到 A 事务在使用【select * from users for update;】语句查询时会出现幻读,但是不加【for update】不会出现幻读。也就是说,幻读是可能会出现的,for update 是当前读。
(5)串行化
- 此隔离级别,避免了所有的问题;
- 但是效率低,因为这种隔离级别会导致事务排队处理,不支持并发;
- 模拟实例:先将全局事务隔离级别设置为 serializable。然后开启两个 dos 命令窗口分别登录 MySQL 来模拟 A、B事务。
-- 1.设置全局隔离级别
set global transaction isolation level serializable;-- 2.然后打开两个 dos 命令窗口,分别登录 MySQL
A 事务 | B 事务 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8. mysql> insert into users(name, gender) values('周子恩', '女'); | |
9. commit; |
在 B 事务执行完 insert 语句后,会等待 A 事务结束,否则 B 事务不会进入下一步。两个事务是串行执行的。
6.可重复读的幻读
(1)说明
- MySQL 默认隔离级别是可重复读,但是不可以完全避免幻读问题;
- 解决幻读的方法:
- 快照读:普通 select 语句都是快照读。通过 MVCC(多版本并发控制) 方式解决幻读,因为在可重复的隔离级别中,事务执行的查询数据与该事务启动时查询的数据是一致的,即使中间有新插入的数据,也不会被查询出来,所以避免了幻读问题;
- 当前读:带有 for update 的 select 语句、insert 语句、alert 语句、delete 语句都是当前读。通过 next-key lock(记录锁 + 间隙锁)的方式解决幻读,因为执行 select …… for update 语句时,会加上 next-key lock。若其他事务在 next-key lock 锁范围内插入了一条记录,则该语句会被阻塞,无法成功插入,所以也避免了幻读问题。
(2)快照读解决幻读
- 在整个事务处理过程中,执行相同的普通 select 语句,都是读取快照;
- 快照是固定某个时刻的数据;
- 原理:底层由 MVCC 实现,开始事务后,执行第一个查询语句后,会创建一个 read view,后续的查询语句利用该 read view,通过该 read view 可以在 undo log 版本链中找到事务开始时的数据,所以事务过程中每次查询到的数据是一致的。
(3)当前读解决幻读
- 每一次都读取最新数据;
- 事务开始,会对查询范围内的数据加锁,不允许其他事务对该范围内的数据进行增、删、改。即该 select 语句范围内的数据不允许并发,只能排队执行。next-key lock,就是 间隙锁 + 记录锁,间隙锁用来保证锁定范围内不允许执行 insert 操作,记录锁保证锁定范围不允许执行 update 和 delete 操作。
(4)出现幻读的两种情况
A 事务与 B 事务,在 A 事务中第一次查询使用快照读,B 事务插入数据。然后在 A 事务中第二次查询使用当前读,则会产生幻读现象;
A 事务与 B 事务,在 A 事务中第一次查询使用快照读,在 B 事务中插入数据。然后在 A 事务中更新 B 事务插入的那条记录。最后在 A 事务中再次使用快照读,则会产生幻读现象。(因为执行 update 操作,会在底层执行一次当前读)。