前面我们了解了MySQL事务和四种隔离级别,以及各个级别会导致的一些问题,不了解的可以戳这里:高性能MySQL学习一(逻辑架构、并发控制、事务、四种隔离级别、多版本并发控制MVCC),今天我们通过例子来直观的感受一下.
首先声明一下,这里安装的是MySQL8,那么我们先来查看一下MySQL的默认隔离级别
mysql> select @@version; //MySQL版本
+-----------+
| @@version |
+-----------+
| 8.0.17 |
+-----------+
1 row in set (0.00 sec)
mysql> select @@global.transaction_isolation; //全局隔离级别
mysql> select @@transaction_isolation; //当前会话的隔离级别
mysql> select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ | REPEATABLE-READ |
+--------------------------------+-------------------------+
1 row in set (0.00 sec)
这里还是把四种隔离级别以及会造成的问题列个表
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
√:可能出现 ×:不会出现
1、未提交读(Read uncommitted)导致的脏读
建立了student表用于本次实验
打开一个窗口A,修改其默认隔离级别为read uncommitted
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)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
+----+------+------+
2 rows in set (0.00 sec)
另开一个窗口B,进行数据更新
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
+----+------+------+
2 rows in set (0.00 sec)
mysql> update student set sex='F' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from student; //更新成功
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | F |
| 2 | lala | M |
+----+------+------+
2 rows in set (0.00 sec)
回到窗口A,查看,可以看到B中未进行提交的修改,即造成了脏读现象
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | F |
| 2 | lala | M |
+----+------+------+
2 rows in set (0.00 sec)
2、提交读(Read committed)导致的不可重复读
打开一个窗口A,修改其默认隔离级别为read committed
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)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
+----+------+------+
2 rows in set (0.00 sec)
进入另一个窗口B,进行数据更新,这里插入数据并提交
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into student values(3,'xixi','F');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
回到窗口A,查询得到了不同的结果,在A的两次查询之间,B更新了一条数据,导致事务A执行同样的操作却得到不同的结果,这就是不可重复读
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
| 3 | xixi | F |
+----+------+------+
3 rows in set (0.00 sec)
3、可重复读(Repeatable read)导致的幻读
打开一个窗口A,修改其默认隔离级别为repeatable read
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)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
| 3 | xixi | F |
+----+------+------+
3 rows in set (0.00 sec)
进入另一个窗口B,进行数据更新并提交
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
| 3 | xixi | F |
+----+------+------+
3 rows in set (0.00 sec)
mysql> update student set name='hehe' where id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from student; //数据更新成功
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
| 3 | hehe | F |
+----+------+------+
3 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
回到窗口A,查看,数据并未更新
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
| 3 | xixi | F |
+----+------+------+
3 rows in set (0.00 sec)
这里明明事务B进行数据更新并提交了,但事务A查询却并看不到,这就产生了幻读现象
4、可串行化(Serializable)
打开一个窗口A,修改其默认隔离级别为Serializable,开始事务
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)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | haha | M |
| 2 | lala | M |
| 3 | xixi | F |
+----+------+------+
3 rows in set (0.00 sec)
另起窗口B,开始事务,进行数据更新,但由于A的隔离级别是serializable,完全锁定了字段,于是B只能等待,超时直接报错
我们让A事务进行提交,然后事务B才能更新成功
mysql> commit; //A事务提交
Query OK, 0 rows affected (0.00 sec)
A事务提交后,解除锁定,B事务可以进行数据更新,但这种锁定也带来了效率低下的问题,因而MySQL的默认隔离级别是可重复读(Repeatable read)