快照读与当前读
可能有读者会疑惑,事务的隔离级别其实都是对于读数据的定义,但到了这里,就被拆成了读和写两个模块来讲解。这主要是因为MySQL中的读,和事务隔离级别中的读,是不一样的。
我们且看,在RR级别中,通过MVCC机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据!这在一些对于数据的时效特别敏感的业务中,就很可能出问题。
对于这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)。很显然,在MVCC中:
- 快照读:就是select
- select * from table ….;
- 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。
- select * from table where ? lock in share mode;
- select * from table where ? for update;
- insert;
- update ;
- delete;
事务的隔离级别实际上都是定义了当前读的级别,MySQL为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。而update、insert这些“当前读”,就需要另外的模块来解决了。
当前读场景
表'user_money'结构
id | int |
user | varchar |
money | int |
表'user_money'数据
id | user | money |
1 | 小明 | 1000 |
2 | 小李 | 2000 |
update -- 当前读
事务A
begin;
select money from user_money where id=1; //(A)
select money from user_money where id=1; //(C)
update user_money set money = 3000 where money = 1000; //(D)
commit;
事务B
begin;
update user_money set money = 2000 where money = 1000; //(B)
commit;
2个事务按照ABCD的顺序执行。
则AC的读取结果为:
1000
1000
小明的最终money为2000. D语句更新不成功,因为update读取的是当前最新版本的数据
for update -- 当前读 (lock in share mode 同理)
事务A
begin;
select money from user_money where id=1; //(A)
select money from user_money where id=1 for update; //(C)
update user_money set money = 3000 where money = 1000; //(D)
commit;
事务B
begin;
update user_money set money = 2000 where money = 1000; //(B)
commit;
2个事务按照ABCD的顺序执行。
则AC的读取结果为:
1000
2000
for update读取的money为2000. 因为for update读取的是当前最新版本的数据