MySQL 事务隔离级别,进阶和基础哪一个难
同样,修改 MySQL 的事务隔离级别也有只修改当前会话和修改全局两种情况:
只修改当前会话
set session transaction isolation level repeatable read;
修改全局
set global transaction isolation level repeatable read;
复制代码
验证用到的表以及数据:
drop database if exists bank;
create database bank;
use bank;
drop table if exists accounts;
create table accounts(
id bigint unsigned auto_increment primary key comment '主键',
owner char(32) not null default '' comment '账户名称',
balance decimal(10,2) not null comment '账户余额',
created_at datetime not null default current_timestamp comment '创建时间',
updated_at datetime not null default current_timestamp on update current_timestamp comment '更新时间'
)engine=innodb charset=utf8 collate=utf8_general_ci;
insert into accounts(owner, balance) values
('A', 100),
('B', 100),
('C', 100);
复制代码
读未提交
首先修改 session 的事务隔离级别:
T1
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED |
+-------------------------+
1 row in set (0.00 sec)
T2
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED |
+-------------------------+
1 row in set (0.00 sec)
复制代码
分别在两个 session 中执行事务,在 T2 中对 ID 为 1 记录进行修改:
T1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:53:07 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
T2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:53:07 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql> update accounts set balance = balance - 10 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 90.00 | 2021-07-24 16:21:58 | 2021-07-24 16:59:23 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
复制代码
在 T 2 提交之前查看 T1 中相应记录的情况:
T1
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 90.00 | 2021-07-24 16:21:58 | 2021-07-24 16:59:23 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
T2
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
复制代码
可以看到,在读未提交事务隔离级别下,T2 中对数据的修改在 T2 提交之前已经在 T1 中体现,因此造成了 T1 中的脏读。
读提交
首先将会话中的事务隔离级别改为读提交:
T1 and T2
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED |
+-------------------------+
1 row in set (0.00 sec)
复制代码
在 T1 和 T2 中分别查看当前表中的数据:
T1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 90.00 | 2021-07-24 16:21:58 | 2021-07-24 16:59:23 |
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
3 rows in set (0.00 sec)
T2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 90.00 | 2021-07-24 16:21:58 | 2021-07-24 16:59:23 |
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
3 rows in set (0.00 sec)
复制代码
此时在 T2 中更新 ID 为 1 的记录并在 T1 和 T2 中分别查看 ID 为 1 的记录:
T2
mysql> update accounts set balance = balance - 10 where id = 1;
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 80.00 | 2021-07-24 16
:21:58 | 2021-07-24 17:13:02 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
T1
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 90.00 | 2021-07-24 16:21:58 | 2021-07-24 16:59:23 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql> select * from accounts where balance >= 90;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 90.00 | 2021-07-24 16:21:58 | 2021-07-24 16:59:23 |
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
3 rows in set (0.00 sec)
复制代码
由此可知,在读提交的事务隔离级别之下,T2 中的修改在 T2 提交之前不会体现在 T1 中。现在,将 T2 提交,然后再在 T1 中执行刚才的查询语句:
T2
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
T1
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 80.00 | 2021-07-24 16:21:58 | 2021-07-24 17:13:02 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql> select * from accounts where balance >= 90;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
2 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
复制代码
再次查看 ID 为 1 的记录,发现 balance 发生了变化,造成了不可重复读。而以同样的条件过滤 balance,结果集中的记录少了一条,造成了幻读。
可重复读
首先更新会话的事务隔离级别:
T1 and T2
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
复制代码
在 T1 和 T2 中分别查看当前表的数据情况,之后在 T2 中更新 ID 为 1 的记录并提交:
T1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 80.00 | 2021-07-24 16:21:58 | 2021-07-24 17:13:02 |
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
3 rows in set (0.00 sec)
T2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 80.00 | 2021-07-24 16:21:58 | 2021-07-24 17:13:02 |
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
3 rows in set (0.00 sec)
mysql> update accounts set balance = balance - 10 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from accounts;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 70.00 | 2021-07-24 16:21:58 | 2021-07-24 17:35:15 |
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
3 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
复制代码
此时查看 T1 中的数据:
T1
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 80.00 | 2021-07-24 16:21:58 | 2021-07-24 17:38:22 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql> select * from accounts where balance >= 80;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 80.00 | 2021-07-24 16:21:58 | 2021-07-24 17:38:22 |
| 2 | B | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
| 3 | C | 100.00 | 2021-07-24 16:21:58 | 2021-07-24 16:21:58 |
+----+-------+---------+---------------------+---------------------+
3 rows in set (0.00 sec)
复制代码
由此可见,可重复读的事务隔离级别成功的避免了不可重复读和幻读的情况。但如果此时在 T1 中对 ID 为 1 的记录的 balance 进行更新操作会发生什么情况呢?
T1
mysql> update accounts set balance = balance - 10;
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> select * from accounts where id = 1;
+----+-------+---------+---------------------+---------------------+
| id | owner | balance | created_at | updated_at |
+----+-------+---------+---------------------+---------------------+
| 1 | A | 60.00 | 2021-07-24 16:21:58 | 2021-07-24 17:47:14 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
复制代码
可见,可重复读的隔离级别保证了更新之后结果的正确性,但却容易让人产生错觉。
串行化
首先修改会话的事务隔离级别:
T1 and T2
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| SERIALIZABLE |
+-------------------------+
1 row in set (0.00 sec)
复制代码
在 T1 和 T2 中分别查询当前表的数据情况,并在 T2 中更新 ID 为 1 的记录:
T1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from accounts;
+----+-------+---------+---------------------+---------------------+
评论