Bootstrap

事务隔离级别实战学习

事务的隔离性实现是最复杂的,也是最难的,所以 MySQL 对隔离性做了四个级别的实现。事务的隔离性其实是指,两个事务之间的操作在未提交时相关不可见。这跟 Java 多线程里的可见性正好相反。MySQL 通过 MVCC、锁等手段

1.1 读未提交(Read uncommitted)

这种事务隔离级别下,读到的数据是其他事务没有提交的数据,所以不需要做特殊处理,可以直接读取当前数据即可。

1.2 读已提交(read committed)

MySQL 通过 多版本并发控制(MVCC) 实现了 一致性非锁定读 ** 的能力。当一个事务对某个记录进行操作时,会对该行记录进行加锁,在 RC 级别下,如果另外一个事务要读取当前数据的话,则不会等待锁释放,而是读取行记录的一个快照版本。所以才叫非锁定读。因为读的是快照数据,所以也叫快照读**。

下面我们开启两个事务看下 RC 级别下的快照读情况。

首先修改 事务隔离级别为 RC 级别,并且设置binlog的模式

SET session transaction isolation level read committed;

然后开启事务 A

begin;
update my_test set name ="李四" where id = 1;

先不提交,然后我们再打开一个事务B。

begin;
select * from my_test;
 
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 张三   |   11 |
+----+--------+------+
1 row in set (0.00 sec)

然后我们提交一下事务A,发现事务B已经能够读取到最新的数据了。

begin;
select * from my_test;
 
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 张三   |   11 |
+----+--------+------+
1 row in set (0.00 sec)

select * from my_test;
 
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 李四   |   11 |
+----+--------+------+
1 row in set (0.00 sec)

也就是说,我们可以直接读取到其他事务锁定的数据,这个就是非锁定读。读取到的数据是其他事务提交后的数据,没有提交的数据读取不到。所以隔离级别也叫读已提交。事务B两次查询请求的结果不一致的现象也叫不可重复读,即同一个事务里两次读取的结果不一致。这个问题在 RR 级别下就会解决。

整个 SQL 执行过程:

1.3 可重复读(repeatable read)

将事务隔离级别调整到 RR 级别。

SET session transaction isolation level repeatable read;

在 RR 隔离级别下可以解决不可重复读的问题。使用的方法也是多版本并发控制(MVCC)

首先开启一个 事务 A。执行 读取数据。

begin;
select * from my_test;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 李四   |   11 |
+----+--------+------+

然后开始事务B,执行更新操作,并提交。

begin;
update my_test set name = "李四2" where id = 1;
commit;

然后事务A再执行读取操作,发现读取的结果没有变化。

begin;
select * from my_test;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 李四   |   11 |
+----+--------+------+

select * from my_test;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 李四   |   11 |
+----+--------+------+

所以是解决了不可重复读的问题。同一个事务里,第一次读取和第二次读取的数据是一致的。

但是如果你使用下面的语句进行查询的话,就会发现会读到最新的数据

select * from my_test lock in share mode;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | 李四2   |   11 |
+----+---------+------+
2 rows in set (0.00 sec)

select * from my_test for update;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | 李四2   |   11 |
+----+---------+------+
2 rows in set (0.00 sec)

这是因为 MySQL 有两种读取方式。一种被称为快照读,一种被称为当前读。在 MVCC 中 就是快照读,读取的是快照数据,而 和 是当前读,会读取当前版本的数据。MySQL 通过 MVCC 实现了上面这种能力。

1.4 串行化(Serializable)

所有 SQL 全部进行加锁处理,读加读锁排他锁,写加写排他锁。这样就不会有并发的问题了。但是性能很差。