购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.5 开始编译MySQL

本节旨在引导读者逐步掌握MySQL内核的调试方法。鉴于MySQL内核源码规模庞大,包含上百万行代码,若无恰当的方法,初学者难以顺利入门。因此,本节将系统地介绍从源码下载、编译、初始化到调试MySQL内核的全过程,确保读者能够逐步深入,最终掌握相关技能。

此外,本节还将分享笔者多年在MySQL内核调试方面的宝贵经验,旨在帮助读者规避常见的误区和陷阱,避免在调试过程中走弯路,从而更加高效地掌握MySQL内核的调试技巧。

1.5.1 下载MySQL源码包

在调试MySQL之前,先下载MySQL源码包。可以从很多渠道下载MySQL源码,但是这里建议从官方网站进行下载。

1.下载方式

具体操作流程如下:

进入MySQL官方网站下载页面(https://dev.mysql.com/downloads/),如图1-1所示。

图1-1 MySQL官方网站下载页面

单击MySQL Community Server项,进入MySQL Server下载页面,如图1-2所示。

图1-2 MySQL Server下载页面

单击Archives按钮,然后选择版本。本书的写作是以MySQL 5.7.19版本的源码为主的,因此这里建议大家也先选择此版本。Operating System(操作系统)选择Source Code,最后的OS Version(操作系统版本)选择All Operating Systems(Generic)(Architecture Independent),Compressed TAR Archive Includes Boost Headers进行下载,MySQL 5.7.19版本的下载界面如图1-3所示。

图1-3 MySQL 5.7.19版本的下载界面

单击Download按钮开始下载。

2.目录说明

下载完源码包之后,用解压命令进行解压:

进入MySQL源码目录中:

可以看到MySQL目录中有很多目录和文件:

MySQL的重要目录如表1-1所示。

表1-1 MySQL的重要目录

(续)

前面介绍了大概的目录,这里主要根据模块来介绍一些核心文件,方便大家在研究不同的模块的时候快速上手。MySQL的重要模块及其解释如表1-2所示。

表1-2 MySQL的重要模块及其解释

(续)

注意,以上文件是MySQL 5.7.19版本的,其他版本的也基本一致,部分文件可能存在出入。

1.5.2 编译MySQL

MySQL的编译过程对于初学者而言确实具有一定的复杂性,因其涉及诸多参数与依赖项。本小节旨在详尽指导用户完成MySQL的编译过程,并深入解析其中一些至关重要的编译参数,以帮助用户更好地理解与实施。

编译环境:

安装依赖包:

如果你下载的MySQL源码不包含boost包,则需要下载,其下载地址为https://sourceforge.net/projects/boost/files/boost/1.59.0/boost_1_59_0.zip/download。

安装完依赖包之后开始编译,创建一个build目录来存放编译后的文件:

先执行cmake:

执行成功之后会有如下输出:

然后执行编译:

如果CPU数量充足,可以指定并行编译:

编译会执行10多分钟,性能较差的计算机可能需要几十分钟,编译成功之后会有如下输出:

然后进行安装,这个过程会把编译好的二进制文件安装到指定的目录中:

安装成功之后有如下信息输出:

然后检查一下最终安装好的二进制文件,进入/usr/local/mysql-5.7.19/bin/目录中:

可以看到MySQL相关的二进制文件及命令行工具都在这个目录中,执行MySQL客户端命令工具:

到此就编译结束了。下面来重点介绍MySQL的重要编译参数及其说明,如表1-3所示。

表1-3 MySQL的重要编译参数及其说明

1.5.3 使用IDE进行调试

现在已经完成了MySQL的编译,接下来可以进行调试了。调试可以借助很多种工具,例如GDB(GNU Debugger,GNU调试器)和一些IDE(Integrated Development Environment,集成开发环境)工具等。

这里推荐使用IDE工具,因为调试的时候更加直观。我们可以根据自己的习惯和喜好选择IDE工具,这里使用的是CLion。下面会使用CLion来简单演示如何调试MySQL。

1.导入编译好的MySQL

单击File→New CMake Project from Sources选项,准备导入MySQL项目如图1-4所示。

然后弹出Select Directory to Import(选择导入目录)对话框,这里就选择我们刚刚编译完成的build目录,如图1-5所示。

选择完后单击OK按钮,然后弹出Import CMake Project对话框,如图1-6所示。

单击Open Existing Project按钮,最终导入完成。之后CLion会加载一段时间,加载完成后就可以准备配置MySQL启动参数了。单击Edit Configurations选项,如图1-7所示。

选择mysqld选项,在右边可以看到一些配置,从这里配置启动参数,如图1-8所示。

在图1-8中的Program arguments文本框中填入对应的启动参数,参数如下:

注意,配置好了还不能直接启动,因为还没有初始化MySQL。

图1-4 准备导入MySQL项目

图1-5 选择build目录

图1-6 弹出Import CMake Project对话框

图1-7 单击Edit Configurations选项

图1-8 配置MySQL启动参数截图

2.初始化MySQL

首先创建MySQL目录:

然后创建一个简单的MySQL配置文件:

再创建MySQL的data和tmp目录、错误日志文件:

创建MySQL用户:

授予MySQL目录的mysql用户权限:

然后进行初始化:

在初始化完成之后查看日志文件,可以看到初始化的MySQL密码:

这表明MySQL已经初始化完成,接下来可以通过IDE来启动。在启动之前,我们在IDE上设置一个断点,断点就设置在MySQL的入口函数中,如图1-9所示。

图1-9 设置断点

断点的设置非常简单,单击代码最左侧的空白位置即可。然后单击IDE的调试按钮进行调试,调试按钮如图1-10所示。

断点调试MySQL如图1-11所示。

可以看到MySQL停在了断点上,这个时候就可以一步步地进行调试了。从这里调试可以看到MySQL的整个启动流程。如果想让MySQL直接启动,则单击CLion的继续按钮,如图1-12所示。

在IDE中启动了MySQL后,在终端通过MySQL客户端进行连接:

图1-10 调试按钮

图1-11 断点调试MySQL

图1-12 直接启动MySQL

这里的密码就是刚刚初始化时生成的临时密码。若需调试SQL语句的执行流程,推荐在客户端触发SQL操作,随后在IDE中设置断点以追踪。此操作的前提是,开发者需对SQL执行过程中可能调用的函数有所了解。以SELECT语句为例,它通常会经过row_search_mvcc方法进行处理。因此,用户可尝试在此方法内设置断点,并执行SQL语句以观察执行流程。

关于断点的设置位置,则需要开发者逐步熟悉并深入了解源码结构。通过持续阅读和分析源码,开发者将能自然掌握在何处设置断点以获取所需的调试信息。

此外,对于调试的启动方式,除了直接在IDE中启动调试外,CLion等IDE还支持attach模式,即允许开发者在终端启动MySQL服务后,通过IDE连接到特定的MySQL进程上进行调试。此模式同样支持断点的设置与调试,为开发者提供了更多的灵活性和选择。对此感兴趣的读者可进一步深入研究并实践。

1.5.4 调试技巧

MySQL是一款拥有上百万行代码的复杂系统,我们掌握一定的调试技巧能够显著提升开发效率。这些技巧是笔者通过不断实践和学习逐步积累而来的,写在这里供广大开发者借鉴参考,减少在探索过程中走不必要的弯路。

起初,笔者主要聚焦于Python脚本及小型项目的编写,当时主要依赖Vim编辑器进行代码编辑,并借助pdb工具进行调试。随着技术的深入,笔者开始涉足Redis的阅读与开发,同样以Vim编辑器为核心工具,但在此基础上引入了更多插件以优化阅读体验,同时转向使用GDB进行调试。

随后,笔者将学习范围扩展至MongoDB,在此过程中,笔者尝试使用VS Code进行代码阅读与调试,以探索新的开发工具与调试方法。最终,在深入MySQL源码的阅读与开发阶段,笔者进一步尝试了Visual Studio、NetBeans以及CLion等多种IDE,以寻找最适合MySQL源码阅读和调试的工具组合。

1.多线程调试

多线程调试主要分为以下两种情况:

❑多线程运行不同的代码 。针对多线程运行不同的代码,调试的时候主要跟踪自己的线程,如遇到其他线程的断点触发切换到其他线程,再将线程切换回来即可。GDB下多线程调试命令如下:

在CLion等IDE下可以直接通过鼠标进行线程的查看和切换。

❑多线程运行相同的代码 。针对多线程运行相同的代码,调试起来就比较麻烦,当你设置一个断点的时候,可能有多个线程触发,这个时候就会干扰你的调试。可以通过在GDB中设置 set scheduler-locking on 命令来强制限定只调试当前线程。

2.调试分布式项目

分布式项目一般会部署多个节点。在处理分布式项目的调试任务时,通常会选取某一节点作为断点调试的焦点。然而,由于分布式系统特有的协议与交互机制,该选定节点有可能面临被剔除或发生异常的风险,从而阻碍我们实施准确调试的进程。针对这一挑战,调试分布式项目主要可遵循以下两大策略:

❑Mock或者单元调试 。有些项目是自己做好了Mock,我们只需要调试一个节点就几乎能调试其所有的功能。如果没有Mock,我们也可以尝试自己做Mock,不过这需要你对该项目非常熟悉。除了Mock,我们还可以采用单元测试来对每个函数进行调试。不过这会有逻辑不连贯、单侧覆盖不全的问题。

❑打印日志 。分布式项目最常用的方式还是打印日志。在一些重要的地方打印日志,就能知道代码的大致执行路径。在厘清整体执行路径后,再在每个方法中打印详细日志即可完成调试。

在调试MySQL的MGR时,就会遇到上述问题。

3.编译优化机制

在调试的时候,打印对应的变量有时会报optimized out的错误,这是因为在编译时指定了优化选项。优化选项是通过gcc/g++指定的。

❑-O0 (字母O后跟一个零),关闭所有优化选项,也是CFLAGS或CXXFLAGS中没有设置-O等级时的默认等级。这样就不会优化代码,通常不是我们想要的。

❑-O1 ,这是最基本的优化等级。编译器会在不花费太多编译时间的情况下试图生成更快、更短的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成。

❑-O2 ,-O1的进阶。这是推荐的优化等级,除非你有特殊的需求。-O2会比-O1多启用一些标记。设置了-O2后,编译器会试图在不增大代码体积和占用大量编译时间的前提下提高代码性能。

❑-O3 ,这是最高、最危险的优化等级。用这个选项会延长编译代码的时间,并且在使用gcc4.x的系统里不应全局启用。自3.x版本以来,gcc的行为已经有了极大的改变。在3.x版本中,-O3生成的代码也只是比-O2快一点点而已,而且在gcc4.x中还未必更快。用-O3来编译所有的软件包将产生体积更大、更耗内存的二进制文件,大大增加编译失败的可能性,或出现不可预知的程序行为(包括错误)。这样做得不偿失,记住,过犹不及。在gcc 4.x中使用-O3是不推荐的。

❑-Os ,这个等级用来优化代码尺寸。它启用了-O2中不会增加磁盘空间占用的代码生成选项。这对于磁盘空间极其紧张或者CPU缓存较小的计算机非常有用,但也可能产生些许问题,因此软件树中的大部分ebuild会过滤掉这个等级的优化。使用-Os是不推荐的。

在MySQL中,将所有Makefiles中的O1、O2、O3替换成O0,这样就关闭了优化。其他项目也是这样,这里我们直接在编译时指定debug即可。

4. core dump

core dump的主要思想是在程序崩溃后,通过调试其生成的core文件,得知当时程序运行的状态,从而找到触发崩溃的条件。

MySQL core dump配置如下:

1)打开Linux的core文件配置。

2)添加MySQL的core_file配置(配置在[mysqld]下面),并重启测试实例。

3)配置suid_dumpable(MySQL通常会以suid方式启动)。

4)设置core文件存放的目录并设置完全控制权限。

5)模拟MySQL的crash场景,执行如下命令。

kill操作执行完成后,终于看到了久违的core文件。

找到core文件后执行:

5. pstack

pstack是一个功能强大的工具,它具备查看运行中程序所有线程堆栈信息的能力。然而,使用该工具时需要谨慎,因为它在捕获进程堆栈的过程中,会导致进程出现短暂的阻塞现象。特别是在处理拥有大量线程的程序时,这一行为可能会对服务的正常运行产生不良影响。pstack命令如下:

在MySQL夯住时,或者看MySQL的后台线程时,就会用到上述命令。实际上,通过上述命令就能找到对应的方法,如果想要调试,就可以到对应的方法中加上断点。

6.调试及阅读代码的思路

总体策略是分层次地阅读,遵循由整体至细节的原则。首先明确调用的基本路径,随后深入核心方法的理解,从而在心中构建出一个清晰的框架体系。对于规模庞大的项目,尤为重要的是采取模块化视角进行审视,细致掌握各模块的功能定位及其实现机制,并在此基础上有针对性地提出疑问。随后,应带着这些问题深入源代码中探寻答案,以确保对项目有全面且深入的理解。

(1)熟悉目录及文件

当我们着手探索一个庞大的开源项目时,首要任务是明确其目录结构及各关键文件的基本功能,以此为基础构建初步的认知框架。这些准备工作将有助于我们在后续的代码阅读与调试过程中保持清晰的思路与方向。MySQL的目录结构及重要文件已在前文中详尽阐述,此处不再重复说明。

(2)熟悉具体功能原理

我们应当全面且系统地收集有价值的资料,以深入理解其实现原理。在审视代码之前,务必确保已充分掌握其背后的逻辑与机制,否则在浏览代码的过程中可能会感到困惑不解。

(3)先看注释再看代码

在一些优秀的项目中,基本上每个方法都会有注释,并且会对每个入参和出参进行说明,例如MySQL的方法:

在看完注释后,我们能大致知道该方法是干什么用的,可以判断要不要继续看下去,如果继续看下去就更容易理解。如果注释没有放在方法前,则可能在头文件定义中。

(4)跟着主线思路走

在代码调试过程中,我们时常会深陷于代码的执行流程,而偏离了初始的调研目标。这会浪费大量时间,尤其是在面对拥有数百万行代码的庞大项目时。然而,值得注意的是,这些项目的核心逻辑代码量往往远小于整体规模。因此,为了高效推进工作,我们需要聚焦于核心要点,并采用以下策略:

持续保持对调研初衷的清晰认知,时刻提醒自己此次代码审查的核心目的。

精准定位核心方法,对于诸如检查、校验、条件判断等辅助性方法,仅需理解其基本功用即可,无须深入探究其实现细节。

一旦识别出核心实现部分,务必记录相应的堆栈信息。此举旨在为后续可能的断点调试工作提供便利,确保能够迅速定位并专注于关键代码段。

(5)利用参数和返回值快速阅读代码

有时候一个方法可能有上千行,如果跟着代码的逻辑走,可能会花费大量的时间。这时就可以通过入参、出参和返回值来快速定位核心代码。

大部分方法的主要作用其实就是处理入参,然后里面可能调用其他方法再将该参数或者处理过的参数传入该方法中,最终都要么有返回值,要么写到出参中。我们只需要跟踪这些参数和返回值,即可快速找到核心逻辑。

例如MySQL中的page_delete_rec_list_end:

上述方法是想要删除一条MySQL的记录,可以看到参数里面有一个rec,也就是要删除这条记录,所以我们只需要跟踪rec这条记录即可快速理清楚核心逻辑。

7.利用单元测试进行调试理解

如果遇到无法直接通过常规手段触发特定代码调试的情况,一个可行的策略是转而利用单元测试来辅助调试过程。

注意 MySQL 的测试框架因其独特性,并不支持此种调试方式。而部分项目则能够采用单元测试作为调试手段。

8.查看代码提交记录

在某些情况下,即便经过多次调试,可能仍难以理解特定的逻辑段。这可能是由于该逻辑段较为复杂或特殊,且缺乏必要的注释说明。为了应对这一难题,可以采用git blame命令来追溯该逻辑段的修改历史,并查找对应的提交记录。通常,提交记录会详细说明此次修改的目的和内容,为理解该逻辑段提供有价值的线索。此外,如果运气较好,我们甚至可能找到对应的MySQL的工作日志 ,它可能包含对该逻辑段的概要说明或详细设计,从而进一步加深我们的理解。

9.理解底层技术

在探讨基础软件时,我们常会发现它们与操作系统或网络交互紧密,且主要聚焦于内存管理与磁盘操作。因此,深入掌握操作系统的内存管理机制、文件系统架构及CPU工作原理等基础知识,将极有利于我们快速领悟相关开源项目的精髓。

对于Linux等底层操作系统的熟悉,是理解诸如MySQL、Nginx等上层应用不可或缺的基石。例如,对epoll I/O多路复用技术有了深入了解后,再研读Redis、Nginx的代码,会发现其高并发设计的核心正是基于这一技术。同样,对Linux磁盘的同步与异步读写机制有了清晰认识后,MySQL中的同步I/O与异步I/O实现也将不再晦涩难懂。

此外,精通TCP及套接字编程能够使我们更加顺畅地理解数据库客户端与服务器之间的交互过程,进而洞察到各类数据库或基于TCP的应用在底层实现上的共通之处。

除了上述操作系统与网络知识外,我们还需熟练掌握一些常用的数据结构,如MySQL索引中广泛应用的B+树、崩溃恢复机制中的红黑树,以及Redis中采用的跳跃表和基数树等。同时,了解基本的算法也至关重要,如MySQL在索引页中查找数据时所使用的二分查找法等。对这些知识与技能的综合运用,将为我们深入理解并优化各类软件系统提供坚实的支撑。

10.重复阅读

对于无法理解的代码逻辑,一般调试3~5遍基本上就能理解,但是遇到一些晦涩难懂的代码,并且没有注释时,可能需要调试10遍以上才能理解。对于已经理解的代码逻辑,隔一段时间再去看,也会有一些不一样的认识。

11.总结笔记

记录笔记是非常重要的,推荐在笔记中记录你阅读、分析、调试的过程,记录你的问题以及你收获到了什么,例如:

核心流程。一定要记录其实现的核心流程,这样方便后续看的时候快速理解。

调用关系。记录调用关系,方便后续调试定位。

另外,对一些实现细节可以添加代码注释,方便后续理解。

12.分享和交流

自己的理解有时候不全面,或者有一些误差,跟别人交流后总能查漏补缺或者纠正错误。 H6FNC6kRMnM0zuayYdSISYNykGwMRu6UN2kyIcC4BB/YEBIAE9LcBwaqEgVd+mfN

点击中间区域
呼出菜单
上一章
目录
下一章
×

打开