什么是mvcc
mvcc即多版本并发控制。他主要在mysql innoDB中提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突,也可以做到不加锁,非阻塞并发读
MVCC模型在MySQL中的具体实现则是由 3个隐式字段(DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID),undo日志 ,Read View 等去完成的
MVCC 只能在READ COMMITTED和REPEATABLE READ两个隔离级别下工作,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行,而SERIALIZABLE则会对所有读取的行都加锁。
当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是未提交读和串行化级别,因为未提交读总是读取最新的数据⾏,⽽不是符合当前事务版本的数据行,而串行级别下的快照读会退化成当前读,会对所有读取的⾏都加锁;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
Read Committed隔离级别:每次select都生成一个快照读。
Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。
快照读的实现方式:undolog和多版本并发控制MVCC
mvcc的原理
隐式字段
DB_TRX_ID: 6字节DB_TRX_ID字段,表示最后更新的事务id(update,delete,insert)。此外,删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已软删除。
DB_ROLL_PTR: 7字节回滚指针,指向前一个版本的undolog记录,组成undo链表。如果更新了行,则撤消日志记录包含在更新行之前重建行内容所需的信息。
DB_ROW_ID: 6字节的DB_ROW_ID字段,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
如上图,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本
undo log
undo log主要分为两种:
- insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃 - update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
Read View
Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
通俗来讲就是我们select的时候,要从版本链中取哪一个
read view参数:
m_ids:没有commit的事务id
min_trx_id:m_ids中最小的id
max_trx_id:是应该赋给下一个的事务id,比如此时m_ids为1,2,3,min_trx_id就为1,max_trx_id为4
creator_trx_id:是谁生成了这个readview,他的id
当为读已提交(RC)隔离级别的时候
在每次执行快照读时生成readview
根据判断规则,最后算出他只能读到trx_id=1,name=张三的那一条,对照图就正好是读已提交。
这个得出最后可以读到trx_id=2,的那一条,这里根据上面的正好也体现出来了,一个事务中的两次select得出的结果不同,是不可重复读。
当为可重复读(RR)隔离级别的时候
仅在第一次执行快照读时生成readview,后续的快照读复用