浅学MVCC

最近想回老家工作,也没咋看就找工作,遇到了好多问MVCC的,一直讲的不太清晰,所以写个笔记记一下MVCC的理解。有什么不对的欢迎评论指正。

MVCC保证事务的隔离性

知识准备

binlog(归档日志)

数据库记录操作的日志,比如什么时候做了什么操作,用户数据同步等

redo log(重做日志)保证事务的持久性

InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力,为append写入,用于数据库恢复

undo log(回滚日志)保证事务的原子性

回滚日志,记录数据在什么时候被什么事务做了什么操作

隐藏字段

DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id

DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log

DB_ROW_ID(6字节):隐藏主键,如果没有设置主键且该表没有唯一非空索引时,就会多一个隐藏主键

read view

m_low_limit_id:系统未分配的下一个事务id,如当前事务中最大值为N,则m_low_limit_id=n+1

m_up_limit_id:活跃事务列表中最小事务id

m_ids活跃事务id列表,即当前未提交的事务的id集合

m_creator_trx_id:创建该 Read View 的事务id

二阶段提交

保证redo log和bin log都有记录或都没记录

可见性算法,判断当前事务是否可以获取到改动后的数据

  1. 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的

  2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5

  3. m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的

  4. 如果 m_up_limit_id <= DB_TRX_ID < m_low_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)

    • 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5

    • 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见

  5. 在该记录行的 DB_ROLL_PTR 指针所指向的 undo log 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空

当前读 读取数据库最新版本数据

  • select lock in share mode
  • select for update
  • update 
  • insert 
  • delete

快照读 读取数据库历史版本数据

  • select

事务隔离级别

  • READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读

测试会发现 在READ-COMMITTED和REPEATABLE-READ下,两个事务操作数据读取结果是不一样的;

READ-COMMITTED: 每次进行快照读的时候都会重新生成readview,所以每次都可以获取到最新数据

REPEATABLE-READ: 只有当前事务第一次进行快照读的时候生成readview,之后的快照读都会使用之前的readview

 

但是MVCC无法解决幻读的问题

比如AB事务同时操作数据库表C

A B
SELECT * FROM C  WHERE ***(假如此时查到3条)
INSERT INTO C VALUES(***) (此时B插入一条)
SELECT * FROM C  WHERE ***(此时还是查到3条)

UPDEAT C SET *** WHERE ***(此时A想去更新刚刚查询到的3条数据)

但是结果是更新了4条 ,吧B刚刚插入的这条更新了

MVCC是无法解决幻读问题的

只能通过加锁来避免

A B
SELECT * FROM C  WHERE *** FOR UPDATE(假如此时查到3条,这个时候已经加锁了)
INSERT INTO C VALUES(***) (此时B插入一条,但是被阻塞了)
SELECT * FROM C  WHERE ***(此时还是查到3条)

UPDEAT C SET *** WHERE ***(此时A想去更新刚刚查询到的3条数据)

结果是更新了3条 

 

幻读问题产生原因

如果事务中操作都是快照度,则不会产生幻读

如果事务中是快照读+当前读,则有可能产生幻读

你可能感兴趣的:(数据库,mysql)