一、实现原理
1、每一行记录都有2个隐藏列:
- DATA_TRX_ID 记录最近更新的这条记录的事务ID
- DATA_ROLL_PTR 表示指向该行回滚段的指针,通过这个指针找到修改记录的那行数据的旧版本。
2、过程
- 插入时将行记录拷贝到undo段中,DATA_TRX_ID指向这个事务的ID,DATA_ROLL_PTR都不动,因为此时只有最新的一个版本,没有其他版本
- 此时更新这行数据,那么DATA_TRX_ID就会指向修改记录的这个事务ID,并且DATA_ROLL_PTR会指向undo log段中刚刚拷贝过来的旧版本记录。
- 如果有多次更新,那么undo log会组成一个链表,里面存储数据的各个版本记录
二、如何实现一致性读 --- ReadView
ReadView是用来判断历史事务链表的记录中,哪些事务可以被当前事务可见。
通过select语句来创建ReadView
1、主要包含以下4个部分:
- m_ids:表示生成ReadView时当前系统中活跃的读写事务id列表,这些事务是没有commit的事务
- min_trx_id:上面m_ids中最小的那个事务
- max_trx_id:readview中应该分配给下一个事务的id值,即这个值不在m_ids中
- creator_trx_id:表示生成该ReadView事务的事务Id
2、ReadView如何判断版本链中哪个版本可用
(1)、trx_id = creator_trx_id时,表示这个事务就是由本身创建的,那当然可见。
(2)、trx_id < min_trx_id,min_trx_id是当前没提交的那些事务中最小的那个,小于这个值,代表这个事务已经提交了,所以可以访问到。
(3)、trx_id > max_trx_id 那这个事务肯定没提交,访问不到。
(4)、min_trx_id <= trx_id <=max_trx_id 如果这个trx_id在m_ids中,那么就访问不到,因为m_ids里是还没有提交的事务,如果在这个区间,但是不在m_ids中,那就证明这个事务提交了,所以可以访问到。例如:
m_ids={1,2,4,5} trx_id=3
这时1,2,4,5都是没有提交的,所以访问不到,但是3不在这里面,但又在这个区间里,3就是提交了的,可以访问到。
3、读未提交(RU)直接读取最新的记录即可,串行化(SERIALIZABLE)需要加互斥锁来访问,因此不需要MVCC的帮忙
4、读已提交(RC)的情况下,每一次select时,都会将当前活跃的事务的加入到ReadView中,都会生成一个新的ReadView。
而可重复读(RP)的情况下,第一次查询时会将活跃的事务加入到ReadView中,只会生成一个ReadView,之后每次查询都是沿用这个ReadView。
三、当前读和快照读
1、 select...lock in share mode (共享读锁)
select...for update
update , delete , insert
当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。
2、快照读就是利用mvcc来读
3、快照读是利用mvcc来实现可重复读的(解决幻读),而当前读,是根据next-key锁来实现的。
一个事务中,第一次执行select..for update,之后另一个线程插入了一条,第一条没有提交时,后面的插入操作一直处于阻塞状态,第一条提交之后,第二条才可以插入。