事务隔离级别有四种,以下图为例,四种隔离级别的读数据状态会是不一样的:
事务 A: |---0---|---1---|---2---|---3---|---4---|
事务 B: |---5---|---6---|---7---|---8---|
查询操作:|读事务开始 <--- 不同的隔离级别在不同的时间点读到不同的状态-->|
事务 A: |---0---|---1---|---2---|---3---|---4---|
事务 B: |---5---|---6---|---7---|---8---|
查询操作: 无论是读事务或者写事务 都会得到排他锁,如果是对同一记录操作,事务A一旦先开始,事务B就不能操作,或者查询也不能操作。
不可重复读的重点是修改:
同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了
幻读的重点在于新增或者删除(导致记录数变化)
同样的条件 , 第 1 次和第 2 次读出来的记录数(强调的是记录数,而不是记录本身,因为读锁的锁粒度是记录自身,而不是整张表)不一样。
通过在写的时候加锁,可以解决脏读。
通过在读的时候加锁(或者MVCC提供旧的提交版本),可以解决不可重复读。
通过串行化,可以解决幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITABLE | 1 | 1 | 1 |
READ COMMITABLE | 0 | 1 | 1 |
REPEATABLE READ | 0 | 0 | 1 |
SERIAL | 0 | 0 | 0 |
默认是隔离级别是read commitable。这个隔离级别,做个试样:
模拟是否可重复读,和幻读:
事务1(窗口1):
mysql> begin
-> ;
Query OK, 0 rows affected (0.00 sec)
mysql> select count(*) from pem_of_plat_73;//执行一次结果数量集的统计,检验是否有幻读
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.00 sec)
mysql> select status from pem_of_plat_73 where pem_id=10154;//执行一次记录的修改,检验是否可重复读
+--------+
| status |
+--------+
| 0 |
+--------+
1 row in set (0.00 sec)
事务2:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update pem_of_plat_73 set status=2 where pem_id=10154;//完成记录修改
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> insert into pem_of_plat_73 (plat_id, pem_id, pem_addon_type, task_id, mtime, status, prev_status) values (73, 10155, 0, 22616, now(), 0, -1);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
事务1:
mysql> select count(*) from pem_of_plat_73; //查询记录数量无变多
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from pem_of_plat_73 where pem_id=10154; //查询记录无修改。
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
这跟我们预想的有一点点不一样,如果REAPEATABLE READ应该是有幻读啊。可是为什么记录数一直是48呢。理论上应该有幻读啊。
InnoDB使用了MVCC版本控制。 MVCC记录了每一次的增删改查。另一个事务的增删改记录的版本号要高于本事务。所以这些另一个增删改记录是没影响的。
MVCC并发控制机制 -多版本并发控制(Multiversion Concurrency Control),每一个写操作都会创建一个新版本的数据,读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回;在这时,读写操作之间的冲突就不再需要被关注,而管理和快速挑选数据的版本就成了 MVCC 需要解决的主要问题。
create table ab(a int primary key, b int);
Tx1:
begin;
select * from ab; // empty set
Tx2:
begin;
insert into ab values(1,1);
commit;
Tx1:
select * from ab; // empty set, expected phantom read missing.
update ab set b = 2 where a = 1; // 1 row affected. //最主要是这一步,通过在本事务更新记录使得MVCC机制select出这条记录
select * from ab; // 1 row. phantom read here!!!!
第二个问题是如果想升级成SERIABLE,比如说两个事务都在增加一个表记录。每条新增记录需要记录之前的数量,以这个数量作为记录的版本号。那么第一次的事务增加记录提交,另一个事务需要及时知道这个数量更新。
其实Mysql提供了这么一种机制,读查询也会得到一个排他锁。语法是select ...for update。MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。
如果事务B insert了一个记录,那么事务A的select统计数量操作会被阻塞。
如果事务A先select一个记录。事务B想insert/update这个记录,也会被阻塞。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。