RC(读已提交)和 RR(可重复读)隔离级别下不同的查询语句行为不同,读取到的数据可能不同,以下是详细分析:
RC(读已提交)隔离级别
1. select * from table where id = 1 for update:
读取类型:不管是否在事务中执行for update,这都是一种当前读,能确保读取的数据是最新的,因为 for update 会对符合条件的记录加排他锁。
因为RC级别下是每次执行for update都会新生成读视图,事务每次执行查询操作,都会读取最新已提交的数据,可以读到其他事务已提交的修改。
2. select * from table where id = 1:
读取类型:快照读(一致性读),基于 MVCC读视图读取的是事务开始时的快照版本。跟 for update一样,可以看到其他事务已提交的修改。
当where定位列id不是索引时,需要进行全表扫描,会涉及到回库操作。对于 for update 语句,会对全表扫描到的符合条件的记录加锁,性能较差。对于普通的 select 语句,会根据MVCC机制,根据undo log版本链读取相应的事务版本数据,虽然不加锁但由于需要扫描全表,性能也会受到影响。
RR(可重复读)隔离级别
1. select * from table where id = 1 for update:
读取类型:当前读。因为for update 对符合条件的记录加锁,确保读取的数据是最新的,并且加锁。
可以读取到其他事务已提交的修改,在事务内,第一次执行完 for update后,后续的 for update 读取会看到相同的已修改结果,不会看到其他事务新的修改。因为加锁,其他事务无法修改,会阻塞或者报错。
与RR级别的区别,可以参考如下事务:
begin;
select * from table where id = 1 for update;
...
select * from table where id = 1 for update;
commit;
如果是RC级别,第一次执行for update时会对满足id = 1 的记录加锁,如果 id 是主键或唯一索引,会加行锁。当该语句执行完毕,锁会被释放,然后第二次执行for update会继续加锁,那么两次读取之间就可能读到其他事务已提交的修改内容。
而RR级别第一次执行for update时会对满足 id = 1 的记录加锁,一直到事务结束。因此RR级别事务一致性较高,而性能较低。
2. select * from table where id = 1:
读取类型:快照读(一致性读),基于事务开始时创建的快照进行读取。
只能读取到事务开始时的数据快照,不能读取到事务开始后其他事务已提交的修改,保证了在同一事务内的可重复读特性。