数据库的读分为两种
当前读
(读取当前的最新的数据)
更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。
除了 update 语句外,select 语句如果加锁,也是当前读。
select * from t1 for update;(加x锁)
select * from test1.t1 where id=1 lock in share mode;(加s锁)
快照读
(很多场景下,不需要读取最新数据,只要读取快照就可以满足(undo))
不加锁的select语句比如select * from t1 where id =1;
具体读取的数据的版本,和事务隔离级别有关系
在RR隔离级别下,读取到的数据是事务开始的时候的快照数据版本。
在RC隔离级别下,读取到的是事务当前最新的快照数据版本。
session1 | session2 |
---|---|
begin; | begin; |
update t1 set name='aaa' where id=1; 获取x锁 | |
select * from t1; session1获取x锁后,session2理论上是无法得到x锁和s锁的,也就是无法读取id=1数据, 但是通过一致性非锁定读,读取到了数据。原理:通过MVCC读取的undo里面的快照版本的数据。 |
MVCC的作用
从上面的实验可以看出,MVCC 就是为了实现读-写冲突不加锁,而这个读指的就是快照读。
InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。
RC和RR隔离级别的区别
RC读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
RR可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
Read View
在不同的隔离级别下,访问数据时,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。
这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
不同隔离级别的实现原理
依赖read view用于隔离级别的实现
(1)什么时候生成Read View
一致性读取(consistent read),根据隔离级别的不同,会在不同的时机建立read view,如下:
RR 事务的第一个select命令发起的时候建立read view,直到事务提交释放
RC 事务的每一个select都会单独建立read view
(2)如何实现多版本
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。
同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。
(3)如何确定Read View是否在该事务可读
RC隔离级别下,每个快照读都会生成并获取最新的Read View,因此可能出现在同一个事务中两次查询的结果不一致的情况。
RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的。