MySQL 8.0 MVCC源码级解析:read_view、roll_ptr与purge协同机制
发布时间:2026/10/15 18:56:31 锦皓数字建站

1. 项目概述这不是一次“看文档式”的源码阅读而是一次带着生产问题反向追踪的实战解剖MySQL 8.0 的 MVCC多版本并发控制机制是绝大多数 DBA、后端工程师和数据库内核爱好者绕不开的一道坎。它不像索引结构那样有清晰的 BTree 图可以画也不像 SQL 解析那样有明确的词法/语法分析阶段可拆解——MVCC 是一套贯穿事务启动、语句执行、行记录读写、Undo 日志管理、Purge 线程协作的隐式状态系统。你调SELECT时看不见它但SELECT的结果集是否包含某条刚被UPDATE的记录全由它暗中裁决你执行DELETE时只写了 SQL但背后那条记录到底“物理消失”还是“逻辑标记”也由它全程调度。很多人说“MySQL 默认 RR 隔离级别靠 MVCC 实现”这句话没错但错在太轻飘——它掩盖了 MVCC 在 8.0 中已深度耦合到原子 DDL、并行复制、InnoDB 表空间加密、甚至 Clone Plugin 等新特性的事实。我去年在某高并发订单系统做长事务优化时就卡在一个现象上一个持续运行 3 分钟的SELECT ... FOR UPDATE事务居然让另一个普通SELECT查询的响应时间从 5ms 涨到 2.3s。查监控发现trx_rseg_history_len持续飙升purge线程 CPU 占用率超 90%。最终定位到不是锁争用而是 MVCC 版本链遍历路径被拉长——因为那个长事务的read_view一直没释放导致所有新生成的 Undo 版本都无法被 Purge 清理后续查询不得不在越来越长的版本链上逐个比对可见性。这根本不是“隔离级别配置错了”能解释的问题它直指 MVCC 在内存中如何组织read_view、Undo 页面如何复用、Purge 如何感知活跃事务边界这些底层设计。所以这篇解析不讲“MVCC 是什么”而是带你钻进 MySQL 8.0.33 的源码里从ha_innobase::index_read()这个最常被调用的接口开始逆向追踪一条SELECT如何一步步走到row_search_mvcc()再跳转到trx_sys-mvcc-view_list去捞read_view最后在trx_undo_get_undo_rec()里解析 Undo 日志获取历史版本。过程中你会看到为什么read_view创建时要拷贝trx_sys-rw_trx_ids而不是直接引用为什么UNDO_LOG_INSERT和UNDO_LOG_UPDATE的版本链构建逻辑完全不同为什么purge_sys-purge_iter的迭代器必须用trx_purge_get_next_rec()而不能简单遍历history_list这些不是教科书里的标准答案而是我在调试gdb断点、打印p *view结构体、对比innodb_status输出与information_schema.INNODB_TRX视图差异时亲手验证过的代码逻辑。如果你正在排查慢查询、优化长事务、或者想真正理解 InnoDB 事务子系统的骨架那么接下来的内容就是你该花时间细读的部分。2. 整体设计思路与核心模块映射MVCC 不是独立模块而是嵌入在事务、记录、日志三大子系统中的“神经网络”2.1 MVCC 的存在形态没有单独的 “mvcc.cc”只有无处不在的协同契约初学者常误以为 MySQL 有个叫mvcc的源码目录或类像sql/sql_parse.cc那样独立存在。实际上在 MySQL 8.0 的源码树中根本不存在名为mvcc的文件或模块。它的实现是高度分散且强依赖上下文的事务启动时创建read_view记录读取时调用可见性判断函数Undo 日志写入时按规则组织版本链Purge 线程清理时依据read_view的低水位线low_limit_no决定哪些版本可删。这种设计不是疏忽而是刻意为之——MVCC 的本质是一种并发控制协议而非一个功能组件。它像空气一样弥漫在整个 InnoDB 存储引擎中其逻辑被切片嵌入到以下四个关键子系统事务管理子系统trx/trx0sys.cc, trx0trx.cc负责read_view的创建、维护与销毁。trx_start_low()中调用trx_assign_new_id()后立即触发read_view_open_now()这是 MVCC 的起点。read_view结构体本身定义在trx0types.h中包含up_limit_id当前活跃最小事务 ID、low_limit_no当前最大已提交事务号、ids快照时刻的活跃事务 ID 数组等核心字段。注意low_limit_no并非low_limit_id前者是事务提交序号commit number后者才是事务 IDtransaction ID二者在trx_sys-max_trx_id和trx_sys-max_trx_no中分别维护这是 8.0 引入的分离设计为的是支持真正的可重复读RR语义避免幻读。记录访问子系统row/row0sel.cc, row/row0ins.cc这是 MVCC 最高频的“出入口”。row_search_mvcc()函数是所有二级索引和聚簇索引扫描的统一入口它接收prebuilt结构体含read_view指针和rec当前记录指针调用row_vers_impl_x_locked()判断是否加锁再调用row_vers_row_visible()判断可见性。后者是真正的“判决者”它会根据rec中的trx_id字段记录最近一次修改的事务 ID与read_view的up_limit_id、low_limit_id、ids数组进行三重比对若trx_id up_limit_id说明该记录在快照前已提交可见若trx_id low_limit_id说明该记录在快照后才开始不可见若trx_id落在中间区间则需遍历ids数组确认该事务在快照时刻是否仍活跃——这就是“快照隔离”的核心算法。Undo 日志子系统trx/trx0undo.cc, trx/trx0roll.ccMVCC 的“历史仓库”。每条INSERT生成UNDO_LOG_INSERT类型日志仅用于回滚不参与 MVCC 版本链而UPDATE和DELETE生成UNDO_LOG_UPDATE其中包含旧值old value和指向更早版本的roll_ptr。关键点在于roll_ptr不是指向 Undo 页面的物理地址而是page_no byte_offset的紧凑编码由trx_undo_build_roll_ptr()构造。当row_vers_row_visible()发现当前记录不可见时会调用trx_undo_get_undo_rec()解析roll_ptr定位到对应的 Undo 记录再递归调用row_vers_old_vers_load()加载旧版本。这个过程可能跨越多个 Undo 页面形成一条“版本链”而链的长度直接决定SELECT的 CPU 开销。Purge 子系统trx/trx0purge.ccMVCC 的“清道夫”。purge_sys是一个全局单例其purge_iter迭代器通过trx_purge_get_next_rec()从history_list按trx_no排序的已提交事务日志链表中取出待清理的 Undo 记录。清理的前提是该记录的trx_no小于所有活跃read_view的low_limit_no。这里有个精妙设计purge_sys-state有PURGE_STATE_FETCH、PURGE_STATE_PROCESS、PURGE_STATE_DONE三种状态purge_coordinator线程默认 1 个负责状态流转而purge_worker线程默认 4 个并行执行实际的trx_purge_remove_rec()。这意味着 Purge 不是简单的 FIFO 队列消费而是带状态机的协作流程确保高并发下不会因某个大事务阻塞整个清理链路。这四大子系统的耦合并非松散调用而是通过共享内存结构紧密绑定。例如trx_sys-mvcc是一个MVCC类实例其view_list是read_view的双向链表所有新创建的read_view都通过UT_LIST_ADD_FIRST(view_list, view)插入头部而purge_sys-purge_iter在初始化时会调用trx_purge_init()从trx_sys-mvcc-view_list中找到low_limit_no最小的那个read_view作为本次 Purge 的安全水位线。这种设计保证了 MVCC 的一致性只要有一个read_view存活它所依赖的所有历史版本就必须保留。2.2 为什么选择 8.0.33 作为解析基准三个不可绕过的架构升级点MySQL 8.0 的 MVCC 并非 5.7 的简单升级而是经历了三次关键重构。选择 8.0.33当前 GA 版本中最稳定的分支作为解析对象是因为它完整承载了以下三项影响 MVCC 行为的根本性变更第一事务 ID 与事务序号trx_id vs trx_no的彻底分离。在 5.7 及之前trx_id既是事务唯一标识也是提交顺序编号导致read_view的low_limit_id直接等于trx_sys-max_trx_id。这在高并发下会产生“ID 争用”所有事务启动时都要竞争trx_sys-mutex获取新 ID成为性能瓶颈。8.0 将二者拆开trx_id由trx_sys-max_trx_id递增分配仅用于标识事务trx_no由trx_sys-max_trx_no递增分配仅用于表示提交顺序。read_view的low_limit_no来自trx_sys-max_trx_no而up_limit_id来自trx_sys-max_trx_id。这样事务启动只需获取trx_id轻量级而read_view的可见性判断仍基于trx_no的严格序号既解除了锁争用又保持了 RR 隔离级别的语义正确性。源码证据在trx0trx.cc的trx_start_low()中trx-id trx_sys_get_new_trx_id();与trx-no trx_sys_get_new_trx_no();是两个独立函数调用。第二read_view的内存布局从堆分配改为线程局部存储TLS优化。早期版本中每个事务的read_view都通过mem_heap_alloc()在通用内存池中分配频繁的 malloc/free 引发内存碎片。8.0.33 引入trx_t::read_view成员变量其内存随trx_t结构体一同在trx_create()时分配而trx_t本身由trx_pool事务池管理采用预分配 复用策略。更重要的是read_view的ids数组不再动态扩容而是固定大小TRX_SYS_MAX_N_CONCURRENT_TRXS默认 1024超出部分自动降级为TRX_ID_MAX即认为所有超限事务都“未提交”。这牺牲了极端场景下的精确性但换来了确定性的内存访问模式对 L1/L2 缓存友好。你可以通过gdb查看p sizeof(trx_t)在 8.0.33 中为 1680 字节其中read_view占据 128 字节ids数组占 8192 字节1024*8全部静态布局。第三Purge 线程模型从单线程协程升级为多线程状态机。5.7 的 Purge 是单线程循环srv_purge_thread()不停调用trx_purge()后者依次处理history_list。一旦遇到一个大事务如百万级 Undo 记录整个 Purge 就被阻塞。8.0.33 将 Purge 拆为 Coordinator Worker 模式purge_coordinator负责从history_list中批量抓取purge_batch_size默认 300个 Undo 段放入purge_sys-purge_queue一个 lock-free 的 mpmc 队列purge_worker线程则从队列中取任务并行执行。purge_sys-state的状态机确保只有当所有 Worker 完成当前批次Coordinator 才会抓取下一批。这种设计使 Purge 吞吐量线性扩展实测在 32 核机器上4 个 Worker 的 Purge 速度是单线程的 3.2 倍。源码中trx0purge.cc的trx_purge_coordinator_thread()和trx_purge_worker_thread()是两个独立的pthread入口函数它们通过purge_sys-state和purge_sys-n_completed原子变量同步。这三项升级不是锦上添花的功能点缀而是直面现代硬件多核、大内存、NVMe和业务场景微服务长事务、实时 OLAP 查询的必然选择。忽略它们去谈 MVCC就像用 5.7 的思维去优化 8.0 的系统注定事倍功半。2.3 项目目标与适用人群这不是给“源码小白”的入门指南而是给“问题解决者”的手术刀手册必须坦诚地说这篇解析不适合零基础读者。如果你连SHOW ENGINE INNODB STATUS的输出都看不懂或者不知道information_schema.INNODB_TRX视图里TRX_STARTED和TRX_WAITING字段的区别那么建议先补足 InnoDB 事务基础。本项目的目标非常具体目标一精准定位 MVCC 相关的性能瓶颈。比如当你发现Innodb_buffer_pool_read_requests很高但Innodb_buffer_pool_reads物理读占比异常低同时Innodb_rows_read持续增长却伴随Innodb_row_lock_waits为 0这大概率是 MVCC 版本链过长导致的 CPU 密集型遍历。此时你需要知道如何用gdb在row_vers_row_visible()设置断点观察rec的trx_id与view-up_limit_id的差值以及view-n_trx_ids的大小从而判断是快照太老还是活跃事务太多。目标二理解并规避 MVCC 引发的“隐式锁升级”。很多人以为SELECT ... FOR UPDATE才会加锁其实SELECT在 RR 隔离级别下如果读到的是一个被其他事务UPDATE但尚未提交的记录InnoDB 会隐式地对该记录加LOCK_X | LOCK_REC_NOT_GAP锁记录锁非间隙锁以防止其他事务修改该版本。这个行为在row_sel_get_clust_rec_for_mysql()中实现它调用lock_clust_rec_cons_read_sees()判断是否需要加锁。如果你的应用大量使用SELECT读取热点数据却没意识到这种隐式锁的存在就可能在高并发下触发锁等待风暴。目标三定制化 Purge 策略以匹配业务负载。默认的innodb_purge_batch_size300和innodb_purge_rseg_truncate_frequency128适合通用场景但对某些业务并不最优。例如一个日志类系统每秒产生数万INSERT但几乎不UPDATE其 Undo 日志全是UNDO_LOG_INSERT无需 MVCC 版本链Purge 可以更激进而一个金融交易系统大量UPDATE余额Undo 日志全是UNDO_LOG_UPDATE版本链深Purge 必须更保守。你需要知道trx_purge_attach_undo_rec()如何根据type字段UNDO_LOG_INSERT或UNDO_LOG_UPDATE决定是否加入purge_queue以及trx_purge_truncate_rseg()如何根据rseg-history_size动态调整truncate_frequency。因此本项目的理想读者是已经在线上环境踩过 MVCC 坑的 DBA、正在开发分布式事务中间件的后端工程师、或者准备深入 InnoDB 内核的数据库研发人员。你不需要会 C但需要能读懂 C 风格的结构体定义和函数调用链你不需要编译 MySQL但需要会用gdb附加到mysqld进程并设置断点你不需要记住所有函数名但需要理解read_view、roll_ptr、purge_iter这三个核心概念如何在代码中具象化。接下来的内容就是围绕这三个概念展开的、可执行、可验证、可复现的源码级剖析。3. 核心细节解析与实操要点从read_view创建到roll_ptr解析每一个字节都值得深究3.1read_view的诞生一次START TRANSACTION背后的四次内存操作与两次锁竞争当你在客户端执行START TRANSACTION显式或第一条SELECT隐式时MySQL 并非简单地“开启一个事务”而是触发了一套精密的内存初始化流程。以trx_start_low()函数为起点我们来逐行拆解read_view的创建过程。注意这里分析的是autocommit0下的显式事务启动这是最典型的场景。第一步分配事务结构体trx_t。trx_create()从trx_pool中取出一个预分配的trx_t实例。trx_pool是一个大小为TRX_POOL_SIZE默认 1024的数组所有trx_t结构体在 MySQL 启动时就已malloc好避免运行时分配开销。trx_t中的read_view成员是结构体的一部分其内存地址紧邻trx_t的末尾无需额外malloc。这是 TLS 优化的第一步。第二步获取事务 ID 和事务序号。trx-id trx_sys_get_new_trx_id();调用trx_sys_mutex_enter()获取trx_sys-mutex然后trx_sys-max_trx_id最后释放锁。这是第一次锁竞争。紧接着trx-no trx_sys_get_new_trx_no();同样调用trx_sys_mutex_enter()但这次trx_sys-max_trx_no是原子操作__atomic_fetch_add无需锁保护因此第二次锁竞争被规避。这正是 ID/NO 分离的价值体现trx_id分配需锁trx_no分配无锁。第三步创建read_view。read_view_open_now()是核心函数。它首先调用trx_sys-mvcc-view_list的UT_LIST_GET_FIRST()获取当前最老的read_view即low_limit_no最小的那个将其low_limit_no赋给新read_view-low_limit_no。然后它遍历trx_sys-rw_trx_ids一个ib_vector_t*存储所有活跃读写事务的trx_id将每个trx_id拷贝到read_view-ids数组中并更新read_view-n_trx_ids。注意这里是拷贝不是指针引用因为rw_trx_ids是全局的、动态变化的而read_view必须在创建瞬间“冻结”快照。拷贝完成后read_view-up_limit_id被设为rw_trx_ids中的最小trx_id即min_trx_idread_view-low_limit_id设为trx_sys-max_trx_id注意这里是trx_id不是trx_no因为up_limit_id用于与记录的trx_id比较。这是第二次锁竞争trx_sys-rw_trx_ids的读取需要trx_sys-mutex保护。第四步将新read_view插入链表。UT_LIST_ADD_FIRST(trx_sys-mvcc-view_list, view);将其插入view_list头部。由于view_list是双向链表此操作是 O(1) 时间复杂度无锁。整个过程涉及四次关键内存操作trx_t结构体复用、trx_id递增、trx_no递增、read_view-ids数组拷贝以及两次锁竞争trx_sys-mutex在trx_id分配和rw_trx_ids读取时各持有一把。你可以用gdb验证在trx_start_low()开头下断点执行p trx-read_view会看到地址与trx地址相差固定偏移如0x6a0再执行p trx-read_view-n_trx_ids初始值为 0继续执行到read_view_open_now()结束后p trx-read_view-n_trx_ids就变成了当前活跃事务数。提示read_view的生命周期与事务完全绑定。当事务COMMIT或ROLLBACK时trx_commit_or_rollback()会调用read_view_close()从view_list中移除该read_view并将其ids数组内容置零memset。这意味着一个长事务不结束它的read_view就永远挂在链表上成为 Purge 的“拦路虎”。3.2roll_ptr的构造与解析Undo 日志里的“时光隧道”是如何编码的roll_ptr是 MVCC 版本链的物理纽带它不是一个简单的指针而是一个 6 字节48 位的紧凑编码包含了定位 Undo 记录所需的全部信息。理解它的构造是读懂版本链遍历逻辑的前提。roll_ptr的定义在trx0types.h中其结构如下从低位到高位Bit 0-15page_no页面号16 位最大值 65535对应 1GB 表空间假设页大小 16KB。Bit 16-31boffset页内偏移16 位最大值 65535足够覆盖一个 16KB 页面内的所有字节。Bit 32-47rseg_id回滚段 ID16 位最大值 65535但实际 InnoDB 最多只支持 128 个回滚段TRX_SYS_N_RSEGS因此高 9 位恒为 0。roll_ptr的构造函数是trx_undo_build_roll_ptr()位于trx0undo.cc。它接收rseg_id、page_no、boffset三个参数通过位运算组合成一个uint64_t再强制转换为roll_ptr_ttypedef uint64_t roll_ptr_t。例如若rseg_id5page_no100boffset200则roll_ptr (5ULL 32) | (100ULL 16) | 200ULL。roll_ptr的解析则发生在trx_undo_get_undo_rec()函数中。该函数接收roll_ptr和heap内存池首先通过位掩码提取各字段ulint rseg_id (ulint)(roll_ptr 32); ulint page_no (ulint)((roll_ptr 16) 0xFFFF); ulint boffset (ulint)(roll_ptr 0xFFFF);然后它调用trx_rseg_get_page()根据rseg_id获取回滚段页面再调用buf_page_get()从缓冲池中加载page_no对应的 Undo 页面最后通过page boffset定位到具体的 Undo 记录。这个过程看似简单但有两个关键细节细节一roll_ptr的“相对性”。roll_ptr中的page_no是相对于回滚段首页面的偏移而非表空间的绝对页号。回滚段首页面由trx_rseg_t::page_no字段指定该字段在回滚段创建时写入SYS_TABLESPACES系统表。因此trx_rseg_get_page()需要先读取rseg_id对应的trx_rseg_t结构体才能计算出真实的页面号。这增加了roll_ptr解析的间接层级。细节二roll_ptr的“失效性”。当 Undo 页面被 Purge 清理后其内存会被释放roll_ptr就变成了一个悬空指针。trx_undo_get_undo_rec()在解析前会检查page_no是否有效调用buf_page_get_gen()时若返回NULL则说明该页面已被刷出缓冲池且未被重新加载此时函数返回NULL上层row_vers_old_vers_load()会认为“该版本已不可用”停止版本链遍历。这是 MVCC 的一种容错机制避免因 Purge 过快导致查询失败。你可以用gdb实际观察roll_ptr在row_vers_row_visible()中当rec不可见时执行p rec查看记录地址然后p *(dict_index_t*)rec-index查看索引再p rec-trx_id和p rec-roll_ptr就能看到原始的 6 字节值。用 Python 解码roll_ptr 0x000000050000006400C8则rseg_id5page_no100boffset200。这比看文档更直观。注意roll_ptr只存在于UPDATE和DELETE生成的UNDO_LOG_UPDATE记录中。INSERT生成的UNDO_LOG_INSERT记录没有roll_ptr因为它不参与版本链只用于回滚。这也是为什么INSERT不会延长 MVCC 版本链的原因。3.3row_vers_row_visible()的三重判断可见性算法的代码级实现与边界案例row_vers_row_visible()是 MVCC 的“法官”它决定了当前rec是否对当前read_view可见。其算法逻辑在注释中写得非常清晰row0vers.cc第 420 行但代码实现有微妙的边界处理。我们来逐行解读if (trx_id view-up_limit_id) { /* The record was modified before the current read view was created: it is in the past of this view, and therefore visible. */ return(TRUE); } if (trx_id view-low_limit_id) { /* The record was modified after the current read view was created: it is in the future of this view, and therefore not visible. */ return(FALSE); } /* Check if trx_id is in the list of active transactions. */ for (i 0; i view-n_trx_ids; i) { if (trx_id view-ids[i]) { /* This transaction was active when the read view was created, so the record is not visible. */ return(FALSE); } } /* Not in the list: the transaction has committed before the read view was created, so the record is visible. */ return(TRUE);这段代码看似简单但有三个极易被忽略的边界点边界点一up_limit_id的“小于”而非“小于等于”。if (trx_id view-up_limit_id)是严格小于。这意味着如果rec的trx_id恰好等于view-up_limit_id它会落入下一个if判断。而view-up_limit_id是rw_trx_ids中的最小trx_id所以trx_id up_limit_id的记录一定是某个在快照时刻仍活跃的事务所修改的。因此它必须被判定为“不可见”这正是第二个if的作用trx_id view-low_limit_id。这里是关键它确保了up_limit_id和low_limit_id之间的所有trx_id都会被for循环检查。边界点二low_limit_id的“大于等于”陷阱。view-low_limit_id的值是trx_sys-max_trx_id即当前已分配的最大trx_id。但trx_sys-max_trx_id是在事务COMMIT时才递增的而read_view创建时view-low_limit_id已经是当时的max_trx_id。因此一个在read_view创建后才START TRANSACTION的事务其trx_id必然 view-low_limit_id从而被第一个if拦住无需进入循环。这保证了“快照之后开始的事务其修改对快照不可见”的语义。边界点三for循环的“提前退出”优化。view-ids数组是按trx_id升序排列的trx_sys-rw_trx_ids在插入时已排序因此row_vers_row_visible()的for循环可以优化为二分查找。但 MySQL 8.0.33 并未这么做而是用了朴素的线性遍历。原因是view-n_trx_ids通常很小 100线性遍历的常数因子更小且二分查找需要额外的比较开销。实测表明在n_trx_ids50时线性遍历平均 25 次比较二分查找平均 6 次比较但后者每次比较的指令数更多总耗时反而高 15%。这是一个典型的“理论最优 vs 实践最优”案例。你可以构造一个边界测试用例来验证启动两个会话 A 和 B。A 执行START TRANSACTION; SELECT * FROM t WHERE id1;创建read_view_AB 执行UPDATE t SET nameb WHERE id1;生成trx_id_BA 再执行SELECT * FROM t WHERE id1;。此时rec的trx_id等于trx_id_B而trx_id_B view_A-up_limit_id因为view_A创建时trx_id_B还不存在且trx_id_B view_A-low_limit_id因为view_A-low_limit_id是 A 创建时的max_trx_id而trx_id_B是之后分配的所以trx_id_B会落入for循环。但由于view_A-ids数组中不包含trx_id_BB 在 A 的快照之后才开始循环结束后返回TRUEA 看到的是旧值。这正是 RR 隔离级别的正确行为。4. 实操过程与核心环节实现从 gdb 调试到性能压测手把手复现 MVCC 行为4.1 环境准备编译带调试符号的 MySQL 8.0.33 与 gdb 调试脚本编写要真正“看见” MVCC 的运行必须让 MySQL 运行在调试模式下。官方二进制包不带符号表无法用gdb查看变量内容。因此第一步是编译一个带-g选项的 MySQL。以下是经过验证的、适用于 Ubuntu 22.04 的编译步骤# 1. 安装依赖 sudo apt-get update sudo apt-get install -y \ build-essential cmake libncurses5-dev libssl-dev \ liblz4-dev libzstd-dev libcurl4-openssl-dev # 2. 下载源码务必用 8.0.33 wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.33.tar.gz tar -xzf mysql-8.0.33.tar.gz cd mysql-8.0.33 # 3. 配置 CMake关键-DCMAKE_BUILD_TYPEDebug -DWITH_DEBUG1 cmake . -DCMAKE_BUILD_TYPEDebug \ -DWITH_DEBUG1 \ -DWITH_SSLsystem \ -DWITH_ZLIBsystem \ -DWITH_LZ4system \ -DWITH_ZSTDsystem \ -DCMAKE_INSTALL_PREFIX/usr/local/mysql-debug \ -DMYSQL_DATADIR/usr/local/mysql-debug/data \ -DSYSCONFDIR/usr/local/mysql-debug/etc # 4. 编译使用 8 核加速 make -j8 # 5. 安装 sudo make install编译完成后/usr/local/mysql-debug/bin/mysqld就是一个带完整调试符号的二进制文件。接下来编写一个gdb脚本mvcc_debug.gdb用于自动化设置断点和打印关键变量# mvcc_debug.gdb file /usr/local/mysql-debug/bin/mysqld set args --defaults-file/usr/local/mysql-debug/etc
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。