// 省略…
private:
typedef UT_LIST_BASE_NODE_T(ReadView) view_list_t;
/** 空闲可以被重用的视图*/
view_list_t m_free;
/** 活跃或者已经关闭的 Read View 的链表 */
view_list_t m_views;
};
ReadView:视图,某一时刻的一个事务快照
class ReadView {
// 省略…
private:
/** 高水位,大于等于这个ID的事务均不可见*/
trx_id_t m_low_limit_id;
/** 低水位:小于这个ID的事务均可见 */
trx_id_t m_up_limit_id;
/** 创建该 Read View 的事务ID*/
trx_id_t m_creator_trx_id;
/** 创建视图时的活跃事务id列表*/
ids_t m_ids;
/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
- 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
trx_id_t m_low_limit_no;
/** 标记视图是否被关闭*/
bool m_closed;
// 省略…
};
增加隐藏字段
为了实现 MVCC,InnoDB 会向数据库中的每行记录增加三个字段:
DB_ROW_ID:行ID,6字节,随着插入新行而单调递增,如果有主键,则不会包含该列。
DB_TRX_ID:事务ID,6字节,记录插入或更新该行的最后一个事务的事务标识,也就是事务ID。
DB_ROLL_PTR:回滚指针,7字节,指向写入回滚段的 undo log 记录。每次对某条记录进行更新时,会通过 undo log 记录更新前的行内容,更新后的行记录会通过 DB_ROLL_PTR 指向该 undo log 。当某条记录被多次修改时,该行记录会存在多个版本,通过DB_ROLL_PTR 链接形成一个类似版本链的概念,大致如下图所示。
源码分析
在源码中,添加这3个字段的方法在:/storage/innobase/dict/dict0dict.cc 的 dict_table_add_system_columns 方法中,核心部分如下图。
增删改的底层操作
当我们更新一条数据,InnoDB 会进行如下操作:
-
加锁:对要更新的行记录加排他锁
-
写 undo log:将更新前的记录写入 undo log,并构建指向该 undo log 的回滚指针 roll_ptr
-
更新行记录:更新行记录的 DB_TRX_ID 属性为当前的事务Id,更新 DB_ROLL_PTR 属性为步骤2生成的回滚指针,将此次要更新的属性列更新为目标值
-
写 redo log:DB_ROLL_PTR 使用步骤2生成的回滚指针,DB_TRX_ID 使用当前的事务Id,并填充更新后的属性值
-
处理结束,释放排他锁
删除操作:在底层实现中是使用更新来实现的,逻辑基本和更新操作一样,几个需要注意的点:1)写 undo log 中,会通过 type_cmpl 来标识是删除还是更新,并且不记录列的旧值;2)这边不会直接删除,只会给行记录的 info_bits 打上删除标识(REC_INFO_DELETED_FLAG),之后会由专门的 purge 线程来执行真正的删除操作。
插入操作:相比于更新操作比较简单,就是新增一条记录,DB_TRX_ID 使用当前的事务Id,同样会有 undo log 和 redo log。
源码分析
更新行记录的核心源码在:/storage/innobase/btr/btr0cur.cc/btr_cur_update_in_place 方法,核心部分如下图。
构建一致性读取视图(ReadView)
当我们的隔离级别为 RR 时:每开启一个事务,系统会给该事务会分配一个事务 Id,在该事务执行第一个 select 语句的时候,会生成一个当前时间点的事务快照 ReadView,核心属性如下:
-
m_ids:创建 ReadView 时当前系统中活跃的事务 Id 列表,可以理解为生成 ReadView 那一刻还未执行提交的事务,并且该列表是个升序列表。
-
m_up_limit_id:低水位,取 m_ids 列表的第一个节点,因为 m_ids 是升序列表,因此也就是 m_ids 中事务 Id 最小的那个。
-
m_low_limit_id:高水位,生成 ReadView 时系统将要分配给下一个事务的 Id 值。
-
m_creator_trx_id:创建该 ReadView 的事务的事务 Id。
源码分析
MVCC 模式下的普通查询主方法入口在:/storage/innobase/row/row0sel.cc 的 row_search_mvcc 方法中,之后的所有源码分析基本都在该方法内。
具体创建视图的方法在 ReadView::prepare,调用链如下:
row_search_mvcc -> trx_assign_read_view -> MVCC::view_open ->
ReadView::prepare,源码如下:
最后,会将这个创建的 ReadView 添加到 MVCC 的 m_views 中。
视图可见性判断:SQL 查询走聚簇索引
有了这个 ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
-
如果被访问版本的 trx_id 与 ReadView 中的 m_creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
-
如果被访问版本的 trx_id 小于 ReadView 中的 m_up_limit_id(低水位),表明被访问版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
-
如果被访问版本的 trx_id 大于等于 ReadView 中的 m_low_limit_id(高水位),表明被访问版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
-
如果被访问版本的 trx_id 属性值在 ReadView 的 m_up_limit_id 和 m_low_limit_id 之间,那就需要判断 trx_id 属性值是不是在 m_ids 列表中,这边会通过二分法查找。如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
在进行判断时,首先会拿记录的最新版本来比较,如果该版本无法被当前事务看到,则通过记录的 DB_ROLL_PTR 找到上一个版本,重新进行比较,直到找到一个能被当前事务看到的版本。
而对于删除,其实就是一种特殊的更新,InnoDB 在 info_bits 中用一个标记位 delete_flag 标识是否删除。当我们在进行判断时,会检查下 delete_flag 是否被标记,如果是,则会根据情况进行处理:1)如果索引是聚簇索引,并且具有唯一特性(主键、唯一索引等),则返回 DB_RECORD_NOT_FOUND;2)否则,会寻找下一条记录继续流程。
其实很容易理解,如果是唯一索引查询,必然只有一条记录,如果被删除了则直接返回空,而如果是普通索引,可能存在多个相同值的行记录,该行不存在,则继续查找下一条。
以上内容是对于 RR 级别来说,而对于 RC 级别,其实整个过程几乎一样,唯一不同的是生成 ReadView 的时机,RR 级别只在事务第一次 select 时生成一次,之后一直使用该 ReadView。而 RC 级别则在每次 select 时,都会生成一个 ReadView。
源码分析
走聚簇索引的核心流程在 row_search_mvcc 方法,如下:
视图可见性判断在方法:changes_visible,调用链如下:
row_search_mvcc -> lock_clust_rec_cons_read_sees ->
changes_visible,源码如下:
判断记录是否被打上 delete_flag 标的方法在:/storage/innobase/include/rem0rec.ic 的 rec_get_deleted_flag 方法中,如下图。
获取记录的上一个版本
获取记录的上一个版本,主要是通过 DB_ROLL_PTR 来实现,核心流程如下:
-
获取记录的回滚指针 DB_ROLL_PTR、获取记录的事务Id
-
通过回滚指针拿到对应的 undo log
-
解析 undo log,并使用 undo log 构建用于更新向量 UPDATE
-
构建记录的上一个版本:先用记录的当前版本填充,然后使用 UPDATE(undo log)进行覆盖。
源码解析
构建记录的上一个版本:trx_undo_prev_version_build,调用链如下:
row_search_mvcc -> row_sel_build_prev_vers_for_mysql -> row_vers_build_for_consistent_read -> trx_undo_prev_version_build,源码如下:
视图可见性判断:SQL 查询走普通(二级)索引
面试必问的 MySQL,你懂了吗? 只分析了走聚簇索引的情况,本文简单的介绍下走普通(二级)索引的情况。
当走普通索引时,判断逻辑如下:
-
判断被访问索引记录所在页的最大事务 Id 是否小于 ReadView 中的 m_up_limit_id(低水位),如果是则代表该页的最后一次修改事务 Id 在 ReadView 创建前以前已经提交,则必然可以访问;如果不是,并不代表一定不可以访问,道理跟走聚簇索引一样,事务 Id 大的也可能提交比较早,所以需要做进一步判断,见步骤2。
-
使用 ICP(Index Condition Pushdown)根据索引信息来判断搜索条件是否满足,这边主要是在使用聚簇索引判断前先进行过滤,这边有三种情况:a)ICP 判断不满足条件但没有超出扫描范围,则获取下一条记录继续查找;b)如果不满足条件并且超出扫描返回,则返回 DB_RECORD_NOT_FOUND;c)如果 ICP 判断符合条件,则会获取对应的聚簇索引来进行可见性判断。
源码分析
普通(非聚簇)索引的视图可见性判断在方法:lock_sec_rec_cons_read_sees,调用链如下:
row_search_mvcc -> lock_sec_rec_cons_read_sees,源码如下:
扩展理解
========
ICP(Index Condition Pushdown)
ICP 是 MySQL 5.6 引入的一个优化,根据官方的说法:ICP 可以减少存储引擎访问基表的次数 和 MySQL 访问存储引擎的次数,这边涉及到 MySQL 底层的处理逻辑,不是本文重点,这边不进行细讲。
这边用官方的例子简单介绍下,我们有张 people 表,索引定义为:INDEX (zipcode, lastname, firstname),对于以下这个 SQL:
SELECT * FROM people
WHERE zipcode=‘95054’
AND lastname LIKE ‘%etrunia%’
AND address LIKE ‘%Main Street%’;
当没有使用 ICP 时:此查询会使用该索引,但是必须扫描 people 表所有符合 zipcode=‘95054’ 条件的记录。
当使用 ICP 时:不仅会使用 zipcode 的条件来进行过滤,还会使用 (lastname LIKE ‘%etrunia%’)来进行过滤,这样可以避免扫描符合 zipcode 条件而不符合 lastname 条件匹配的记录行 。
ICP 的官方文档:https://dev.mysql.com/doc/refman/8.0/en/index-condition-pushdown-optimization.html
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。
因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。**
因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
[外链图片转存中…(img-O47sZHS0-1712529021788)]
[外链图片转存中…(img-jPB3zQ8q-1712529021788)]
[外链图片转存中…(img-q6xV5Zsn-1712529021789)]
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-kOOTkTll-1712529021789)]