ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL源码贡献实操手册:从环境搭建到提交补丁全流程

MySQL源码贡献实操手册:从环境搭建到提交补丁全流程 1. 写在前面为什么我会想给 MySQL 提交代码先交代一下我的背景省得大家觉得我是个理论派。我做了十来年的数据库运维和内核开发相关的工作日常被 MySQL 的各种问题折磨过无数遍。遇到性能瓶颈、诡异的复制延迟、崩溃恢复失败的时候第一反应往往是在社区搜答案搜不到就翻官方文档再不行就上源码库里翻翻代码。翻的次数多了心里就会冒出一个念头与其每次靠别人修不如我自己直接动手改。这个念头一旦冒出来就再也压不下去了。真正让我下定决心系统性搞懂 MySQL 源码贡献流程的契机是一次线上事故。当时一个客户的业务表数据量过了亿级某个复杂 join 查询在低峰期也要跑十几秒我们用尽了索引优化、分库分表的路数收效甚微。我顺手在 MySQL 的 Bug 系统里搜了一圈发现有类似反馈但补丁迟迟没人提。那一刻我意识到光是会用MySQL 是不够的想真正解决这一类问题就得能读懂它的执行计划生成逻辑甚至亲手改掉它。于是我给自己定了个目标从读懂源码到提交第一个 patch走通全流程。这篇文章不是给你讲什么高深理论而是把我这一路踩过的坑、查过的文档、反复试错总结出来的经验完整地整理成一份能照着做的实操手册。不管你是刚入门想看看 MySQL 源码长什么样的学生还是在生产环境被坑了无数次想亲自下场修 bug 的工程师这份手册都能帮你少走很多弯路。2. 从哪开始源码获取、环境准备与工具链2.1 拉源码前的三个心智准备在做任何技术准备工作之前我觉得有三件事得先在脑子里理顺否则后面很容易半途而废。第一MySQL 源码量非常大。光 MySQL 8.0 的代码就有几百万行涉及 C、C、Java主要是测试和部分工具、shell 脚本等。你不需要全部读懂关键是找到自己要改的那条线。用官方的话说MySQL 的代码按照功能域划分成很多模块比如存储引擎InnoDB、MyISAM、优化器sql/optimizer.cc 等、复制sql/rpl_*、连接管理、GIS、全文索引等等。第一次打开源码目录的时候我最直观的感受是像走进一座图书馆而不是像读一本书。第二贡献代码不是只有提交 patch这一个环节。一个完整的贡献流程包括理解问题、写代码、写测试、过代码规范、跑测试集、提交补丁、接受社区 review、反复修改、最终合入。这中间最花时间的往往不是写代码而是写测试和等 review。我的第一个补丁代码改动只有几十行但相关的测试、注释、格式调整花了整整一倍多的时间。第三MySQL 社区欢迎各种层级的贡献但要求是有建设性的、规范化的。不是说非得改多么核心的代码才有意义哪怕是修复一个文档笔误、补充一条注释、完善一个错误信息只要是有价值的改进社区都会认真对待。但反过来如果你的补丁没有测试、格式不对、没有说明白为什么这么改被打回也是很正常的。所以心态要摆正这是一个专业切磋的社区不是写个作业交上去就完事的地方。2.2 获取源码的两种方式和我的选择我自己在实际操作中尝试了两种方式一种是从 GitHub 的镜像仓库克隆另一种是直接从 MySQL 官网下载源码包。两者的底层代码是一致的但使用场景有区别。如果你打算长期跟进社区动态、准备多次提交补丁我强烈建议用 GitHub 的官方镜像地址是 github.com/mysql/mysql-server。这个仓库每天同步官方代码issue 和 PR拉取请求流程跟社区保持一致。克隆的时候我推荐用浅克隆加完整历史结合的方式具体操作是git clone https://github.com/mysql/mysql-server.git cd mysql-server git remote add upstream https://github.com/mysql/mysql-server.git git fetch upstream完整克隆的好处是切换分支、查看历史、做代码对比非常方便。唯一的缺点是首次下载体积大网络不好的时候可能要等很久。我当时的办法是放在外接 SSD 上顺便把 git 的压缩缓存关掉某些环境下 git 的自动 gc 会卡很久这样整个拉取过程能快不少。如果你只是想在某个特定版本上做实验不想维护一个巨大的 git 仓库那么从官网下载源码 tar 包也是可以的。到 MySQL 官网的下载页面选择 Source Code 选项卡找到对应版本的源码包下载后解压即可。这种方式干净利落但不方便跟踪社区分支的快速变化。我在实际工作中是两种方式都用日常开发用 Git 仓库复现某个特定版本问题时用官网源码包。2.3 依赖安装和编译环境搭建含避坑记录拿到源码后第一步不是直接看代码而是把编译环境搭起来。因为只有能编译、能运行你自己的 MySQL 版本后面做的任何修改才能验证。依赖环境因操作系统而异我主要讲 Linux 下的情况这也应该是大多数 MySQL 开发者使用最多的环境。在 CentOS/RHEL 系列系统上我通常用 yum 安装下面这组依赖yum install -y gcc gcc-c cmake bison ncurses-devel libaio-devel openldap-devel openssl-devel numactl-devel在 Ubuntu/Debian 系列上则是apt install -y build-essential cmake bison libncurses-dev libaio-dev libldap-dev libssl-dev这里有几个特别容易踩的坑我一个个说。第一个坑是 cmake 版本过低。MySQL 8.0 对 cmake 版本要求比较高如果你系统自带的 cmake 版本太老configure 阶段会直接报错。解决办法很简单去 cmake 官网下载预编译的二进制包解压后把 bin 目录加到 PATH 里或者指定使用绝对路径。我一般会把新版本 cmake 安装到 /usr/local/cmake 下面然后在编译时显式指出export PATH/usr/local/cmake/bin:$PATH cmake --version第二个坑是 bison 版本问题。MySQL 源码里 parser 的生成依赖 bison版本不对会导致生成出来的解析器代码有问题。这个坑我在第一次编译时遇到了报错信息说 bison 版本不兼容。后来我直接把系统自带的 bison 卸载装了一个官方文档推荐的版本问题就解决了。第三个坑是内存和磁盘空间。编译 MySQL 8.0 是一件比较吃资源的事情建议至少准备 8GB 内存和 30GB 到 50GB 磁盘空间。我自己曾经在只有 4GB 内存的虚拟机上尝试编译结果编译到一半直接 OOM进程被杀。后来我把编译线程数调低比如 -j4 甚至 -j2并且关闭了一些不必要的特性才勉强编过。编译本身很简单在源码根目录建一个 build 目录然后在里面执行 cmake 配置和 makemkdir build cd build cmake .. -DWITH_BOOSTsystem -DCMAKE_BUILD_TYPEDebug -DWITH_UNIT_TESTSOFF make -j4这里我特意说明几个选项的意义。-DWITH_BOOSTsystem表示使用系统自带的 Boost 库如果你不想装 Boost也可以让 cmake 自动下载但我建议还是单独装好系统 Boost因为自动下载在大项目里会出现网络中断导致的反复失败。-DCMAKE_BUILD_TYPEDebug是为了后续调试方便如果你只是验证功能用 Release 也可以但 Debug 模式能提供更丰富的符号信息对源码贡献者来说很有价值。-DWITH_UNIT_TESTSOFF是先关掉单元测试的编译加快首次编译速度。等你真正需要跑测试了再单独开起来。编译成功后你会得到 mysqld、mysql_client_test 等一系列二进制文件。到这里你的开发环境就算准备好了。2.4 编译跑通后的基本验证方法环境搭好之后很多人会洋洋得意地直接开改代码。我建议不要急先花十几分钟验证一下这个自己编译出来的 MySQL 能不能正常启动、能不能建表查数。这一步的意义在于如果你基于一个跑不起来的基线开始开发那后面所有问题都会混在一起根本分不清是你的改动引起的还是原本环境就没弄好。验证方法很简单cd build mkdir -p /tmp/mysql-data ./runtime_output_directory/mysqld --initialize-insecure --datadir/tmp/mysql-data ./runtime_output_directory/mysqld --datadir/tmp/mysql-data --socket/tmp/mysql.sock --port3307 然后用客户端连上去./runtime_output_directory/mysql --socket/tmp/mysql.sock -u root进去之后执行SELECT VERSION();如果返回的版本号里带上了你自己编译的源码的版本信息就说明环境基本可用了。注意Debug 模式编译出的 mysqld 启动时可能比 Release 模式慢很多而且日志里会有大量断言信息这是正常的不用慌。只要你没看到致命的错误就说明环境是健康的。3. 源码结构地图第一次进 MySQL 代码库怎么看3.1 顶层目录和它们各自的分工当你第一次进入 mysql-server 的源码目录面对几十个文件夹很容易觉得无从下手。我按自己的理解给你画一个地图。sql/这是 MySQL 服务端的核心代码所在包括连接处理、SQL 解析、优化器、执行器、错误处理等。如果你关心 SQL 语法怎么被解析、执行计划怎么生成主要就是看这里。storage/存储引擎的代码都在这个目录下比如storage/innobase就是 InnoDB 引擎的实现注意它沿用了历史命名没有叫 innodbstorage/myisam是 MyISAM 引擎。绝大部分数据完整性、事务、锁相关的代码都在 InnoDB 里。libmysql/客户端库的实现C API、各类语言的驱动都依赖它。mysys/一些基础系统库比如文件操作、线程封装、内存分配等辅助函数。vio/虚拟 IO 层负责网络 socket 和 SSL 之类的底层输入输出。include/公共头文件。unittest/单元测试代码包含大量业务模块的测试用例。mysql-test/MySQL 官方的集成测试框架包含大量.test和.result文件。提交功能类补丁时你的改动往往需要配套增加或调整这里的测试用例。client/mysql 命令行客户端、mysqldump 等工具的源码。第一次不需要把这些全看完而是带着问题去定位。比如你想研究MySQL 执行一条 select 语句的流程入口通常在sql/mysqld.cc里的 main 函数然后跟着协议解析走到dispatch_command再到mysql_parse、mysql_execute_command最后落到优化器和执行器。我每次给别人讲源码结构时都会强调源码阅读不是线性读完而是像蜘蛛织网一样从一个点扩散开去。3.2 核心模块快速定位方法从一条 SQL 出发我实际操作中觉得最有效的定位方式是从一条查询语句的完整生命周期出发去追代码。下面拿一条最简单的查询举例SELECT * FROM t WHERE id 1;这条语句进入 MySQL 后大致会经历网络通信vio 层→ SQL 解析sql/lexer、sql/sql_yacc.yy→ 语义检查与权限验证sql/sql_parse.cc、sql/sql_authorization.cc→ 优化sql/sql_optimizer.cc→ 执行sql/sql_executor.cc→ 存储引擎接口调用sql/handler.cc 和具体引擎代码。如果你要改的是优化器相关逻辑就要重点关注sql/sql_optimizer.cc和sql/opt_range.cc如果你要改 InnoDB 的行锁、事务实现代码在storage/innobase下。定位速度靠的是对模块的熟悉没有捷径。但在第一次你可以用grep配合cscope或ctags快速跳转。我自己喜欢用cscope在源码根目录执行cscope -R然后在 cscope 的交互界面里输入函数名、宏名就能快速跳到定义处。更现代的方案是用 VS Code 的 Remote-SSH 插件配合 clangd也能获得很不错的跳转体验。3.3 读懂 MySQL 代码风格的几条经验读 MySQL 源码的时候你可能会发现它和现代 C 代码有些不一样的地方。这主要是因为 MySQL 历史悠久从 C 演进来保留了大量 C 风格的写法另外 MySQL 内部有很多代码生成器生成的代码风格也有自己的特色。文件命名和目录结构变化很大。比如 InnoDB 的代码分散在 storage/innobase 下而解析器等核心在 sql/ 下。不要用别的开源项目的经验硬套。变量命名方面MySQL 的代码比较随意有的用很简短的缩写比如thd表示 THD线程描述符trx表示事务对象dict表示数据字典。刚开始看会有点懵但熟悉后会觉得挺顺口。函数注释往往不完整。MySQL 源码中不少关键函数是没有详细注释的更多时候需要你通过调用关系和变量名来推断意图。这一点特别重要看代码时不要把文档注释当唯一依据要多看调用上下文。4. 从发现 bug 到定位问题一次完整的排查实录4.1 我是如何从社区 bug 列表里挑到第一个目标的准备尝试提交补丁之前我先去 MySQL 官方 Bug 系统上刷了一段时间。这里有不少 open 状态的 bug质量参差不齐有些已经有人提交过补丁有些则连复现步骤都不清楚。作为新手选第一个目标很关键。我的经验是挑那些问题描述清楚、影响面明确、改动范围不大的 bug。我当时选的是一个关于mysql -u -p 执行 SQL 超时相关的连接关闭问题具体 bug 编号就不列了因为后来合入版本有变动。现象是客户端在特定环境下执行完 SQL 后连接没有按预期关闭导致连接池里的连接被占用随后出现超时。这个问题虽然不深奥但涉及连接状态转移和线程事件等待对理解 MySQL 的连接处理很有帮助。选 bug 的三个标准我总结如下问题容易复现最好能从描述中直接构造出最小复现场景。复现不了的 bug改起来心里没底。作用域集中只涉及一个模块、一两个函数的改动适合新手快速建立信心。社区反馈活跃bug 帖子下有维护者或用户的评论说明这个问题有价值合入概率高。4.2 定位问题的三个核心工具日志、gdb、源码搜索定位问题时我用得最多的工具组合是客户端日志 服务端错误日志 gdbGNU 调试器 源码 grep。四者各有用途。服务端错误日志一般会记录下错误号、栈信息、线程 ID。拿到这些信息后我通常会直接去源码里搜错误码对应的宏名。比如错误码E0434352这是 Windows 上的一种错误表示形式实际对应内存访问相关的错误码这类东西如果是 MySQL 内部的断言信息在源码里搜一下就能定位到触发断言的位置。gdb 在追问题里价值巨大。特别是当问题表现为服务端崩溃或断言失败时用 gdb 跑一个测试场景崩的时候打印调用栈能迅速把问题锁定在某条代码路径上。gdb ./runtime_output_directory/mysqld (gdb) run --datadir/tmp/mysql-data --socket/tmp/mysql.sock --port3307 (gdb) btbt打印的调用栈就是一份最佳寻宝图。结合日志和栈再回到源码里查看对应函数通常很快就能找到可疑点了。4.3 定位后的思路梳理我的第一个 bug 在定位后发现问题出在连接协议的状态切换逻辑上。当客户端正常发送 QUIT 命令时服务端处理完关闭连接的动作不够彻底在某些网络异常情况下没有清理线程的事件等待状态导致连接池认为连接还活着但实际上服务端已经不再响应。修复思路就是调整状态机的判断条件在收到 QUIT 命令后增加一次状态清理同时确保不会影响到正常关闭连接的路径。到这个阶段我才真正体会到虽然 bug 现象是超时但根源却在连接状态管理。所以在提交补丁之前一定要把问题的根因和表面现象之间的关系理清楚否则很容易写一个只缓解表面症状的补丁过不了维护者的 review。5. 动手写补丁代码修改、测试编写和格式规范5.1 写代码前必须搞懂的四件事拿到明确的问题定位后别急着敲键盘。动手之前我给自己列了一个必须回答的问题清单每一条不搞清楚就不写代码这个改动会影响哪些调用方我把函数放在哪里改才能最小化对外部行为的影响有没有现成的辅助函数或类可以复用MySQL 内部有不少工具函数和封装能复用就不要新写。这个功能在单元测试或集成测试里有没有对应的测试框架可以用我应该新增测试还是修改现有测试社区维护者对这类问题的编码风格偏好是什么比如更喜欢用哪种错误码、哪种日志级别我把这些问题逐条查清楚后才真正开始写代码。我的第一个补丁实际改动在一个函数内新增了一个分支大约几十行 C 代码。改动虽然不大但涉及对另一个函数接口的调用所以我还顺手补了函数注释说明改动前后行为的变化。5.2 测试在 MySQL 源码贡献里有多重要我可以负责任地告诉你没有测试的补丁基本不会被接受。MySQL 社区对补丁的最低要求之一就是必须配套测试。测试类型主要分两种单元测试位于unittest/目录下针对单个函数或类做验证。优点是运行快、定位精确适合纯逻辑型改动。集成测试位于mysql-test/目录下用.test脚本驱动真实的 MySQL 服务端执行 SQL并把输出和.result文件比对。这种方式贴近真实使用场景MySQL 官方大量功能测试都采用这种方式。以我这个链接问题为例集成测试的写法是创建一张简单的表执行一次查询然后模拟客户端异常断开再检查连接池是否有新的连接可以正常建立。写测试用例的时候我会先在 mysql-test 里跑一遍确认它在我修改前的代码上失败、在修改后的代码上通过。做到先失败后成功才能证明这个测试真的覆盖了问题。5.3 代码规范细节format、copyright、commit messageMySQL 代码有一套自己的格式化习惯比如缩进用的是四个空格不是 tab、行宽一般不超过 100 字符、函数名和变量名采用小写加下划线的风格。此外MySQL 要求大部分新文件需要有版权声明头改动已有文件时也要注意不要破坏原作者的声明。提交信息commit message也是 review 的一部分。一个合格的 commit message 应该包含三类信息动机这个补丁解决什么问题、实现摘要怎么解决的、测试信息做了哪些验证。我在写第一个 commit message 时参考了社区里其他人提交的历史记录发现他们通常会用Bug#xxxxx Fix ...开头然后列出改动文件和测试结果这种方式很值得学习。6. 提交补丁流程从分支到 Pull Request6.1 分支管理和提交的推荐姿势MySQL 社区现在主要通过 GitHub 的 Pull Request 来接受代码贡献当然也有邮件列表的讨论形式但对个人贡献者来说 GitHub 是最顺滑的路径。我的推荐操作流程是这样的创建自己的开发分支。不要在 master 分支上直接改代码而是从最新的 master 拉一个临时分支git checkout -b fix-connection-quit在分支上完成代码修改和测试后提交到本地仓库git add sql/sql_parse.cc mysql-test/suite/xxx.test git commit -m Bug#xxxxx Fix connection status leak on QUIT command After receiving QUIT command, the connection state was not cleaned up properly in some network error cases, causing the connection pool to think the connection was still alive. This fix adds an extra state cleanup step when handling QUIT. Reviewed-by: ...把本地分支推送到你自己的 GitHub 仓库git push origin fix-connection-quit然后到 GitHub 页面上点击 Pull Request 按钮选择从你的分支向 mysql-server 的 master 分支创建 PR。6.2 Pull Request 的 title 和 description 怎么掌控PR 的 title 要简洁明确我通常采用Bug#xxxxx: short description的格式因为搜索引擎和邮件列表一般都能通过 bug 编号找到关联信息。description 则要展开叙述包括复现步骤和实际效果期望行为代码修改的简要说明测试计划自测结果。另外还要注意一点PR 之前最好把代码 rebase 到最新的 master 上避免冲突。每次 rebase 后记得重新编译、重新跑测试确保你没有把别人的改动搞坏。我吃过这个亏中途 rebase 后没重新跑测试提交之后 CI 直接报红最后只好灰溜溜地又补了一版。6.3 社区 review 过程是怎样的提交 PR 后你可能会收到两类反馈一类来自自动化的 CI 工具会检查编译、单元测试、格式等另一类来自真正的人类维护者会从代码逻辑、边界情况、性能影响、兼容性等角度给出意见。我第一次收到维护者 review 意见时心里还挺紧张后来发现他们的反馈通常非常具体比如你在这里加了锁但应该考虑另一个线程同时在读这个变量或你用的这个变量名有歧义建议改成 xxx之类。这个阶段的修改不需要感到被冒犯这是社区帮你提高代码质量的免费机会。认真回每一轮评论修改后再 pushPR 会自动更新。一个 PR 经过三轮左右的 review 是常事中间可能涉及多次 force push 或追加提交。我个人的习惯是每轮修改后重新组织提交历史把零散的修改 squash 成一个或几个清晰提交这样维护者看起来更舒服。7. 常见问题与排查技巧实录7.1 编译相关我踩过的坑cmake 版本过旧报错信息通常带有CMake 3.x is required的字样。解决方法是去 cmake 官网下载新版并在 PATH 里优先使用新版。bison 版本不兼容MySQL 源码里的 parser 对 bison 有版本要求如果版本不对可能在编译阶段出现模糊的错误提示。建议按官方文档建议的版本安装。Boost 路径问题如果指定了-DWITH_BOOST/path要确保指向的是 Boost 源码目录而不是安装后的 include 目录。Debug 编译内存不足编译时降低并行度-j2并增大临时 swap 空间能有效缓解 OOM。7.2 测试相关常见的失败原因和定位手段测试结果文件不一致往 mysql-test 新增用例时第一次跑会生成.result文件但如果业务逻辑在不同环境和版本上有差异比对可能失败。这时需要人工确认差异是否合理合理就更新.result不合理就说明代码有 bug。测试超时mysql-test 框架对每个测试有默认时间限制如果用例涉及大数据量或大量并发可能需要手动调大超时参数。但千万不要为了通过测试而无限调高超时这属于投机取巧。测试顺序依赖有些测试用例之间会存在相互影响比如某测试把全局变量改了没恢复导致后面的测试失败。遇到这种情况要在测试结束时清理现场。7.3 提交前最后的五步自检清单提交前我强烈建议按照下面这个清单逐项打勾[ ] 重新编译一次确认没有告警和错误[ ] 运行与本次改动相关的单元测试和集成测试确认全部通过 -- [ ] 检查自己改动的代码是否符合 MySQL 风格包括缩进、命名、注释[ ] 补丁的说明文档注释、commit message是否足够清楚[ ] 是否已经 rebase 到最新 master 分支并且没有引入无关变更。8. 从第一个补丁到持续贡献我的体会与建议我依然记得第一个 PR 被合入的那个周末CI 全绿维护者点了 Approve然后合并按钮亮起来的那一刻成就感是难以形容的。但这个过程中让我收获最大的反而不是我的代码进了知名开源项目这件事而是我在被 review 的过程中学会了如何更严谨地思考边界条件、如何写出更容易被他人理解的代码。如果你正在考虑走上 MySQL 源码贡献这条路我的建议很简单不要等自己足够厉害再开始。很多人在准备阶段就卡住了觉得源码太大、环境太复杂、自己水平不够。但实际上社区里每一个专家都是从一个最小的补丁开始的。你只需要选定一个描述清楚的小 bug按照我上面说的流程走一遍就能获得脱胎换骨的体验。另外千万不要把贡献代码当成一件孤立的事情。多参与社区讨论、多读别人提交的 PR、多关注邮件列表里的问题这些都能帮你更快建立起对整个项目的直觉。我在后续处理另一个存储过程相关的问题时就是因为之前读过一对关于分隔符处理的 PR才能在几分钟内定位到解析器里对应的代码块。9. 一份终极版手册之外可以继续深入的方向写到这里如果你想继续深入我可以给你指几个方向。加入 MySQL 的开发者邮件列表观察日常技术讨论关注每年度的 MySQL 社区团队会议和贡献者活动参与线上或线下交流从 bug 系统里挑选难度系数更高的任务涉足优化器、复制、InnoDB 等核心模块尝试为 MySQL 写一个全新的插件或存储引擎这是最能锻炼综合能力的方式之一。我个人在实际操作中的体会是贡献 MySQL 源码这件事表面上是一系列技术动作的拼接本质上却是一场和世界顶级工程师思维碰撞的训练。你提交的不仅仅是一段代码更是一份经过严谨论证的工程决策。希望这份手册能够成为你走上这条路的起点。
返回列表