事务的隔离性实现是最复杂的,也是最难的,所以 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 中 select * from my_test
就是快照读,读取的是快照数据,而select * from my_test lock in share mode;
和 select * from my_test for update;
是当前读,会读取当前版本的数据。MySQL 通过 MVCC 实现了上面这种能力。
1.4 串行化(Serializable)
所有 SQL 全部进行加锁处理,读加读锁排他锁,写加写排他锁。这样就不会有并发的问题了。但是性能很差。
评论