MVCC基本概念
当前读
读取的是最新版本,读取时保证其他并发事务不能修改当前记录,会对读取记录加锁比如时常操作的出select...语句之外的共享锁和排他锁都是当前读。
快照读
简单的select语句(不加锁)就是快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
Read Committed(RC):每次都生成一个快照读。
Repeatable Read(RR):开启事务的第一个select语句就是快照读的地方。
Serializable:快照读会退化到当前读。
MVCC
多版本并发控制。维护一个数据的多个版本,使读写没有冲突,快照读使MySQL实现MVCC提供了一个非阻塞读功能。MVCC具体实现依赖数据库记录中的三个隐式字段、undo log日志、readView。
MVCC核心
表三个隐藏字段
DB_TRX_ID | 记录操作该数据事务的事务ID; |
DB_ROLL_PTR | 指向上一个版本数据在undo log 里的位置指针; |
DB_ROW_ID | 隐藏ID ,当创建表没有合适的索引作为聚集索引时,会用该隐藏ID创建聚集索引; |
DB_ROW_ID是数据表无主键无唯一约束时自动创建的
undo log
回滚日志,记录了事物修改之前的数据信息,当事物回滚时候,通过undo log日志回滚数据。
当insert的时候,产生undo log日志只在回滚时需要,当事务提交后,可立即删除。
update、deleed时,产生undo log 日志不仅在回滚需要,在快照读时也需要,所以不会立即被删除。
undo log版本链
user表数据
id | name | age | DB_TRX_ID | DB_ROLL_PTR |
1 | abc | 10 | 1 | 0x00001 |
版本链
![](https://img-blog.csdnimg.cn/img_convert/bf02a9932c62f8ed6f576c94d4a86cc0.png)
不同事务或相同事务对同一条记录修改,会导致该记录的undo log生成一条记录版本链表,链表的头部时最新的旧记录,链表尾部是最早的旧记录。
readView
ReadView是快照读SQL执行时MVCC提取数据的依据,记录并维护当前活跃事务(未提交的)id.
ReadView中四个核心字段
字段 | 含义 |
m_ids | 当前活跃事务id集合 |
min_trx_id | 最小活跃事务id |
max_trx_id | 预分配事务id,当前最大事务+1(因为事务id是自增的) |
creator_trx-id | ReadView创建者的事务id |
版本链数据访问规则
trx_id 代表当前undolog版本链对应事务ID
![](https://img-blog.csdnimg.cn/img_convert/b50722e87b963e0a5683903c0a191bf8.png)
不同的隔离级别,生成ReadView的时机不同
READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
RC
![](https://img-blog.csdnimg.cn/img_convert/a3843bf81e01e60ec5d0d54df1f00e1c.png)
RR
RR级别下只有第一次执行快照读时生成ReadView,后续复用该ReadView。
![](https://img-blog.csdnimg.cn/img_convert/546f7315af4508461ca353f2f38a2243.png)
MVCC是否解决了幻读
严格意义上并没有解决幻读MVCC利用版本链,undo log,Read View可以在快照读模式下解决幻读问题,并且不用加锁解决读写冲突问题,极大的增加了数据库的并发量。 但在当前读模式下仅仅依靠MVCC不能解决幻读问题,必须依赖next-key锁(行锁+GAP锁)来解决,这是因为当前读必须获取最新数据。
所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog 版本链、ReadView来实现的,而MVCC + 锁(next-key ),则实现了事务的隔离性。 而一致性则是由redo log 与 undolog保证