目录
一、3个记录隐藏字段
二、undo 日志
三、read view
一、3个记录隐藏字段
本片文章是帮助理解上篇文章Mysql隔离性的辅助知识。
mysql在建表时,不仅仅创建了表的结构,还创建了3个隐藏字段。
DB_TRX_ID :6 byte,最近修改( 修改/插入 )事务ID,记录创建这条记录/最后一次修改该记录的事务ID
DB_ROLL_PTR : 7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就 行,这些数据一般在 undo log 中)
DB_ROW_ID : 6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引
DB_TRX_ID该字段占6个字节,其中存储最近修改或插入的事务ID,也就是你当前对表进行插入或者修改此时的事务ID就会被存储到该字段中。
DB_ROLL_PTR占7个字节,为指针指向该记录的上一个版本。
DB_ROW_ID占6字节,如果我们建表时没有主键,InnoDB会生成一个隐藏的主键,根据该主键来建立聚簇索引。
假设当前的建立表为:
实际的三个隐藏字段会这样存储:
我们目前并不知道创建该记录的事务ID,隐式主键,我们就默认设置成null,1。第一条记录也没有其他版本,我们设置回滚指针为null。
二、undo 日志
MySQL的日志是具有功能性和保存数据的功能的。这里的数据包括sql语句。
我们这里理解undo log,简单理解成,就是 MySQL 中的一段内存缓冲区,用来保存日志数据的就行。
undo log主要解决两个问题:
1.回滚
2.MVCC ---》多版本控制 ---》来解决隔离性和 隔离级别
我们上文表格中的数据为 1 , a。
此时,来了一个事务10,准备修改 a 为 abc。undo log就要发挥作用。
事务10,因为要修改,所以要先给该记录加行锁。
修改前,现将改行记录拷贝到undo log中,所以,undo log中就有了一行副本数据。
事务10提交,释放锁。
现在又有一个事务11,准备修改 abc为dcb。
事务11,因为也要修改,所以要先给该记录加行锁。
修改前,现将改行记录拷贝到undo log中,所以,undo log中就又有了一行副本数据。此时,新的 副本,我们采用头插方式,插入undo log。
事务11提交,释放锁。
这样,我们就有了一个基于链表记录的历史版本链。所谓的回滚,无非就是用历史数据,覆盖当前数 据。 上面的一个一个版本,我们可以称之为一个一个的快照。
有了这个版本链,那回滚就可以依靠它来执行,想要回滚时直接将先前的数据覆盖现在的数据即可。
那我在insert等其他修改时,其他事务要select,此时select读取,是读取最新的版本呢?还是读取历史版本?
当前读:读取最新的记录,就是当前读。增删改,都叫做当前读,select也有可能当前读.
快照读:读取历史版本(一般而言),就叫做快照读。
我们可以看到,在多个事务同时删改查的时候,都是当前读,是要加锁的。那同时有select过来,如果也要读取最新版(当前读),那么也就需要加锁,这就是串行化。 但如果是快照读,读取历史版本的话,是不受加锁限制的。
那么,是什么决定了,select是当前读,还是快照读呢?隔离级别.
那么,如何保证,不同的事务,看到不同的内容呢?也就是如何如何实现隔离级别?
三、read view
Read View就是事务进行 快照读 操作的时候生产的 读视图 (Read View),在该事务执行的快照读的那一 刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
Read View 在 MySQL 源码中,就是一个类,本质是用来进行可见性判断的。 即当我们某个事务执行快照 读的时候,对该记录创建一个 Read View 读视图,把它比作条件,用来判断当前事务能够看到哪个版本的 数据,既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。
简化后的结构体如图所示:
该事务执行的快照读的那一 刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID,其中最小的id放在up_limit_id中,最大的id放在low_limit_id中。
我们现在拥有的数据版本链(undo log中的链式结构)以及当前快照读的 ReadView。
我们将read view中的事务id与数据版本链中id做对比,就能知道当前快照读所能读到的内容。
在画图之前要记住
/** 高水位,大于等于这个ID的事务均不可见*/
trx_id_t m_low_limit_id
/** 低水位:小于这个ID的事务均可见 */
trx_id_t m_up_limit_id;
我们举个例子。
事务4:修改name(张三) 变成name(李四)
当 事务2 对某行数据执行了 快照读 ,数据库为该行数据生成一个 Read View 读视图
//事务2的 Read View
m_ids; // 1,3
up_limit_id; // 1
low_limit_id; // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID creator_trx_id // 2
此时版本链是:
现在要是对比,看事务二是否能读到版本链中的哪一个。
//事务2的 Read View
m_ids; // 1,3
up_limit_id; // 1
low_limit_id; // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID creator_trx_id // 2
对比:
up_limit_id<DB_TRX_ID<=low_limit_id;
并且DB_TRX_ID不在m_Ids中。
证明,事务2可以读取事务4修改之后的版本。
整篇文章的流程为:
我们知道了建表时会创建三个隐藏的记录字段用来记录事务的ID、地址与隐藏的主键。这三个会帮助实现回滚,回滚需要用到undo log,undo log中存放的一个个副本数据成链状表示。在不同的隔离级别下,不同事务读到的数据不一样,需要read view来协助,在read view中的low_limit_id,up_limit_id与undo log中的DB_TRX_ID做对比,来判断能读到什么版本。