概念
MVCC是InnoDB用于多版本并发控制,数据库通过它能够做到遇到并发读写的时候,在不加锁的前提下实现安全的并发读操作,是一种乐观锁的实现方式,能大大提高数据库的并发性能,还可以解决脏读,不可重复读的事务隔离问题。
生效的隔离级别
MVCC只在读已提交(Read Committed)和可重复读(Repeatable Read)两个事务隔离级别下有效
tips:“读未提交”和“串行化”有视图概念吗?
-
“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念。
-
“串行化”隔离级别下直接用加锁的方式来避免并行访问,也没有视图概念。
实现原理
主要是依赖行记录中的两个隐式字段、 undo log中的版本链、ReadView 来实现的。
两个隐式字段
-
在InnoDB中,每行数据都会被添加3个字段
-
DB_TRX_ID :插入或更新行的最新一个事务ID. (
解读
:用于MVCC的ReadView判断事务id) 此外, 删除在内部被视为更新,其中行中的一个特殊位被设置为将其标记为已删除. -
DB_ROLL_PTR:回滚指针. (
解读
:用于MVCC中指向undo log记录) 指向已写入回滚段(rollback segment)的一条undo log记录, 记录着行(row)更新前的副本. -
DB_ROW_ID:隐藏的自增 ID. (
解读
:对于MVCC可忽略该字段) 如果InnoDB自动
生成聚集索引, 则索引包含这个行ID值. 否则, DB_ROW_ID列不会出现在任何索引中.
-
Undo log版本链
-
insert undo log insert undo log是指在insert操作中产生的undo log, 仅用于事务回滚. 因为insert操作的记录, 只对事务本身可见, 对其它事务不可见, 所以该日志可以在事务commit后直接删除. 不需要进行purge(后台清除线程)操作。
-
update undo log update undo log是对delete和update操作产生的的undo log. 该undo log除了用于事务回滚,可能还需要提供MVCC机制, 因此不能在事务commit后就进行删除. 提交时放入undo log链表,等待purge线程(后台清除线程)进行最后的删除。
每次对记录进行改动,都会记录一条undo日志,就算是该记录的一个旧版本,这条undo log日志包括数据行旧值、roll_pointer(回滚指针 , insert操作对应的undo日志没有该属性,因为该记录并没有更早的版本)、trx_id(事务id)。随着更新次数的增多,回滚指针可以将这些undo日志都连起来,串成一个链表,链表称之为版本链,版本链的头节点就是当前记录最新的undo版本。
ReadView
ReadView也叫一致性读视图。
创建时机
-
在“可重复读”隔离级别下,整个事务存在期间都用这个视图。
-
在
begin/start transaction
命令下,ReadView是在事务启动时创建的(或者说是在执行第一个普通的select语句(快照读)时创建的)。 -
在
start transaction with consistent snapshot
命令下,ReadView是在执行这个命令时立即创建的。
-
-
在“读提交”隔离级别下,每次执行普通的select语句时都会生成一个独立的ReadView
判断可见性&查找版本链
MVCC在普通的select语句时才生效。当执行普通的select语句时, ReadView中包含如下4个字段并根据如下规则判断undo log版本链中的哪个版本对当前事务是可见的。如果某个版本的数据对当前事务不可见的话,那就顺着版本链往前找到上一个版本的数据,继续按照如下规则判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
字段 | mysql原始字段 | 含义 | 可见性 | 可见性结论说明 | 备注 |
---|---|---|---|---|---|
max_trx_id | m_low_limit_id | 高水位,应该分配给下一个事务的id 值 | >=它的,都不可见 | trx_id >= max_trx_id:如果trx_id 值小于 Read View 中的 min_trx_id ,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见 | 活跃事务:开启但未提交的事务 |
min_trx_id | m_up_limit_id | 低水位,活跃事务最小事务id | <它的,都可见 | trx_id < min_trx_id:如果 trx_id 值小于 Read View 中的 min_trx_id ,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见 | |
creator_trx_id | m_creator_trx_id | 创建readview的事务id | =它的,都可见 | trx_id = creator_trx_id:如果 trx_id 值等于创建Read View的事务Id,那么数据记录的最后一次操作的事务就是当前事务,该版本的记录对当前事务可见 | 只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会 为事务分配事务id,否则在一个只读事务中的事务id值都默认为0 |
m_ids | m_ids | 创建readview时活跃事务的id列表 | 当min_trx_id <= trx_id < max_trx_id时,在它里面的不可见,否则可见 |
| |
MVCC如何实现读已提交和可重复度隔离级别
根据上面所述,MVCC可以通过行记录的2个隐式字段、undo log版本链、ReadView生成时机以及判断可见性规则保证不同时刻启动的事务readview之间互不影响,从而实现了已提交和可重复度隔离级别。
标签:事务,log,实现,undo,trx,版本,MVCC,原理,id From: https://blog.csdn.net/aofeng9/article/details/143919373