目录
概念
MVCC的具体实现
隐式字段
undo log日志
概念
undo log版本链
readview
接上文,redo log保证了事务的持久性,undo log 保证了事务的原子性和一致性
那,隔离性是如何保证的呢?
锁:排他锁(如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁)
mvcc : 多版本并发控制
概念
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突
mvcc最主要的功能就是在多个事务并发的情况下,确定应该并发的访问哪个版本
MVCC的具体实现
主要依赖于数据库记录中的隐式字段、undo log日志、readView。
隐式字段
我们在表中自定义了id,age,name字段,但其实内部还隐藏了三个mysql提供的字段
隐藏字段 | 含义 |
DB_TRX_ID | 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。 |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。 |
undo log日志
概念
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,mvcc版本访问也需要,不会立即被删除。
undo log版本链
假如此时同时有四个事务同时操作。
先来看事务2,此时会先把原来的数据记录到日志中
事务3,此时会重新记录旧数据
事务4也是同理,综上所述,我们可以知道:不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
但是,问题来了?事务5呢,事务5进行了两次查询,两次查询返回的内容一样吗?执行的结果一样吗?这个就不是undo log版本链可以决定的了,他只是做一个记录,具体的判断依据就是readview。
readview
ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。
当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。
快照读
简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
而这个也与隔离级别有关
Read Committed:每次select,都生成一个快照读。
Repeatable Read:开启事务后第一个select语句才是快照读的地方。
四大核心字段:
字段 | 含义 |
m_ids | 当前活跃的事务ID集合 |
min_trx_id | 最小活跃事务ID |
max_trx_id | 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的) |
creator_trx_id | ReadView创建者的事务ID |
以图中标红位置为例,当执行到红框位置时,
m_ids就是事务3,事务4,事务5
min_trx_id就是事务3
max_trx_id就是事务6(预分配事务,最大事务+1)
creator_trx_id就是事务5
其中,ReadView为了能获取快照读中最准确的数据,它定义了一些数据访问的规则
不同的隔离级别,生成ReadView的时机不同
READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
RC隔离级别下,在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
面试回答
面试官:事务中的隔离性是如何保证的呢?(你解释一下MVCC)
候选人:事务的隔离性是由锁和mvcc实现的。 其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写 操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段, 第二个是undo log日志,第三个是readView读视图。
隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事 务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回 滚指针),指向上一个版本的事务版本记录地址
undo log主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个 版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本, 通过roll_pointer指针形成一个链表
readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规 则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读 是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快 照读时生成ReadView,如果是rr隔离级别仅在事务中第一次执行快照读时生 成ReadView,后续复用