第1章 MySQL 内核简介
本章首先带领读者了解MySQL 内核的发展历程。随后,带领读者进行MySQL 的下载与编译操作,并进行相应的调试。最后分享笔者在过往多年间所积累的有关调试的宝贵经验。
1.1 MySQL 内核历史
MySQL 的起源可追溯至1979 年,当时其作者Monty 在TcX 公司任职。他最初开发了一个面向报表的存储引擎,并持续在该公司工作至1990 年。1990 年,一个客户提出需求,希望该报表存储引擎支持SQL。面对这一挑战,Monty 决定自主开发SQL 层,尽管初期成效不佳,但他通过不懈努力,持续优化,最终在1996 年发布了MySQL 1.0 版本,初期主要面向内部客户。同年10 月,MySQL 3.11 版本问世,年底则推出了首个基于Linux 的MySQL 版本。
此后,MySQL 迅速崛起,被移植至多个平台,并为公司带来了一定的收益。1998 年,MySQL 发布了多线程版本,并全面支持多种语言的API(Application Program Interface,应用程序接口),标志着其在商用领域的进一步成熟。1995 年,MySQL AB 公司成立,并在2001 年与Sleepycat 合作开发了Berkeley DB 引擎(后来著名的InnoDB 存储引擎的前身),该引擎支持事务处理,使MySQL 在1999 年开始支持事务。
进入21 世纪,MySQL 持续进化。2000 年,MySQL 整合了ISAM(Indexed Sequential Access Method,索引顺序存取法)引擎,即现在的MyISAM 引擎,使得Server(服务器)层与MyISAM 的耦合度显著提升。2008 年,MySQL AB 公司被Sun 公司收购;随后在2009 年,Sun 公司又被Oracle 收购。值得注意的是,Oracle 对MySQL 的收购早有预兆,早在2005 年便收购了InnoDB 团队。
自2010 年起,MySQL 发布了多个重要版本。MySQL 5.5 将InnoDB 设为默认存储引擎;MySQL 5.6 新增了在线的数据定义语言(Data Definition Language,DDL)、索引下推(Index Condition Pushdown,ICP)、多范围读取(Multi-Range Read,MRR)等功能以提升性能;MySQL 5.7 GA 则支持了MySQL 组复制(MySQL Group Replication,MGR)和多元复制等特性。2016 年发布的MySQL 8.0 更是进行了重大重构,包括改进数据字典、支持原子DDL 以及引入Hash Join 等功能。
2024 年MySQL 发布了9.0 版本,支持使用JavaScript 编写存储过程,引入VECTOR 向量类型以及移除mysql_native_password 插件等。
[!注意] 自MySQL 5.7 版本以来,新增功能相对较少,而MySQL 的主要工作重心已转向性能优化。
1.2 MySQL 内核衍生
在不断演进中,MySQL 吸引了众多用户群体。然而,鉴于用户需求的多样性、业务场景的差异性以及侧重点的不同,MySQL 无法全面覆盖所有用户的具体需求。因此,从MySQL 中衍生出了多个分支版本,其中较为流行的如下:
-
MariaDB :MySQL 的创始人Monty 在离开Sun 公司后维护的一个开源分支,其命名源自Monty 的女儿Maria。MariaDB 聚焦于性能优化,在性能层面相较于社区版MySQL 有显著提升,如引入了Hash Join 及Semi Join 的优化措施。为防范MariaDB 重蹈MySQL 的覆辙,陷入过度商业化的困境,Monty 创立了一个基金组织来负责其管理。该组织严格遵循非商业原则,资金主要来源于各大公司会员的慷慨赞助,从而确保了MariaDB 的持续开源与免费属性。
-
Percona :由Percona 公司推出的另一个MySQL 开源分支。Percona 公司起初专注于咨询业务及数据库周边工具如XtraBackup 和Percona Toolkit 的开发。因此,Percona 分支的主要目标在于提升MySQL 的维护与诊断能力,通过提供性能诊断工具、增加参数与命令控制选项以及针对极端场景的性能优化等手段,为用户带来更好的使用体验。此外,Percona 还独立维护了一个名为XtraDB 的存储引擎,该引擎基于InnoDB 进行开发,进一步增强了数据库的性能。
这两个MySQL 分支均保持了与MySQL 的高度兼容性,确保了业务在这些分支之间的平滑迁移。然而,值得注意的是,高版本的MariaDB 可能存在一定程度的兼容性问题。因此,在选择分支版本时,应基于具体的产品需求进行考量。若无明显偏好,建议默认使用官方版本,因其生态系统相对完善,且商业机制的有效运作保障了其可持续发展的能力。
此外,在国内市场,一些大型企业及云服务商也根据自身业务需求,维护了各自的MySQL 分支版本。随着近年来数据库国产化需求的日益增长,还涌现出了一批基于MySQL 定制开发的创业公司,致力于解决特定行业场景下的数据库问题。
1.3 MySQL 内核版本
一款优秀的软件往往依赖于一个健全且可持续的版本管理体系,如同Linux、MySQL 等经典软件所展现的那样。它们的版本管理流程虽各有特色,但核心思想一致:通过不同版本的发布来区分功能迭代,同时维护稳定版与开发版。
具体到MySQL 内核的版本管理,其规则如下:
- 首个数字代表主版本号,这一数字的变动通常伴随着重大新特性的引入,如从5.x 跃升至8.x、9.x。主版本号的更迭往往间隔数年,是软件发展历程中的重要里程碑。
- 次位数字为发行级别,当软件新增功能或发生不兼容变更时,此数字随之增加。例如,MySQL 5.6 到5.7 的过渡即反映了发行级别的提升。每个发行级别内部,通常会经历数十个小版本的迭代。
- 末位数字则代表发行系列内的具体版本号,用于标记小特性的添加与漏洞的修复。例如,MySQL 5.7.19 至5.7.20 的更新即属于此类范畴。一般而言,此类小版本的发布周期为数月。一旦某个发行序列超出其维护期限,其版本号将不再递增。
至于如何区分MySQL 的稳定版与开发版,关键在于理解其发行序列的不同阶段:
- Milestone 阶段,标志着新发行序列的初步亮相,如MySQL 5.7.1 至5.7.6。此阶段的版本尚不成熟,建议仅用于实验与测试,避免直接部署于生产环境。
- Release Candidate 阶段,预示着软件即将达到正式可用(General Availability,GA)状态。例如,MySQL 5.7.7 与5.7.8 即处于此阶段。此时,软件内部已历经充分测试与修复,稳定性显著提升,但仍可能存在少量待解决的问题。
- GA 阶段,如MySQL 5.7.9 及后续版本,即表示该发行序列已正式成为稳定可靠的版本,可供用户广泛采用。
[!注意] 自MySQL 8.0 起,MySQL 引入了新的版本模型,并推出了两种类型的版本:
- MySQL 创新版,旨在持续更新功能特性,确保用户能够及时体验到软件的最新进展。例如,MySQL 8.1.0 即被规划为首个创新版本。
- MySQL 长期支持(Long-Term Support,LTS)版,专注于提供必要的修复与稳定性保障,为那些追求稳定运行环境的项目与应用提供有力支持。在MySQL 8.0 的生命周期内(预计于2026 年4 月结束),8.0.34 及之后的版本将专注于错误修复工作,直至最终成为LTS 版本。此举旨在为用户从8.0.x 迁移至8.x LTS 版本提供充足的时间与便利。
1.4 MySQL 内核社区
如果你想加入到MySQL 开发中,那么以下是一些有用的资源:
- MySQL 社区论坛(https://mysqlcommunity.slack.com/),在这里你可以讨论各种问题,并且可以找到很多有经验的开发者。
- MySQL 官方开发者邮件列表 (mysql-dev-subscribe@lists.mysql.com),可以在这里讨论各种问题。
- MySQL 官方支持(https://support.oracle.com/portal/),可以在这里提交各种问题。
- MySQL 漏洞提交(https://bugs.mysql.com/),可以在这里提交各种问题,以及尝试向MySQL 提交漏洞修复代码。
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. 目录说明
下载完源码包之后,用解压命令进行解压:
tar -zxvf mysql-boost-5.7.19.tar.gz进入MySQL 源码目录中:
cd mysql-5.7.19/可以看到MySQL 目录中有很多目录和文件:
-rw-r--r--@ 1 zbdba staff 33704 6 22 2017 configure.cmake
-rw-r--r--@ 1 zbdba staff 13832 6 22 2017 config.h.cmake
-rw-r--r--@ 1 zbdba staff 88 6 22 2017 VERSION
-rw-r--r--@ 1 zbdba staff 2478 6 22 2017 README
-rw-r--r--@ 1 zbdba staff 333 6 22 2017 INSTALL
-rw-r--r--@ 1 zbdba staff 66241 6 22 2017 Doxyfile-perfschema
-rw-r--r--@ 1 zbdba staff 17987 6 22 2017 COPYING
-rw-r--r--@ 1 zbdba staff 26727 6 22 2017 CMakeLists.txt
drwxr-xr-x@ 47 zbdba staff 1504 6 22 2017 libevent
drwxr-xr-x@ 4 zbdba staff 128 6 22 2017 libbinlogstandalone
drwxr-xr-x@ 9 zbdba staff 288 6 22 2017 libbinlogevents
drwxr-xr-x@ 106 zbdba staff 3392 6 22 2017 include
drwxr-xr-x@ 17 zbdba staff 544 6 22 2017 extra
drwxr-xr-x@ 17 zbdba staff 544 6 22 2017 dbug
drwxr-xr-x@ 3 zbdba staff 96 6 22 2017 cmd-line-utils
drwxr-xr-x@ 46 zbdba staff 1472 6 22 2017 cmake
drwxr-xr-x@ 34 zbdba staff 1088 6 22 2017 client
drwxr-xr-x@ 16 zbdba staff 512 6 22 2017 BUILD
drwxr-xr-x@ 18 zbdba staff 576 6 22 2017 plugin
drwxr-xr-x@ 10 zbdba staff 320 6 22 2017 packaging
drwxr-xr-x@ 17 zbdba staff 544 6 22 2017 mysys_ssl
drwxr-xr-x@ 109 zbdba staff 3488 6 22 2017 mysys
drwxr-xr-x@ 19 zbdba staff 608 6 22 2017 mysql-test
drwxr-xr-x@ 21 zbdba staff 672 6 22 2017 libservices
drwxr-xr-x@ 12 zbdba staff 384 6 22 2017 libmysqld
drwxr-xr-x@ 15 zbdba staff 480 6 22 2017 libmysql
drwxr-xr-x@ 32 zbdba staff 1024 6 22 2017 zlib
drwxr-xr-x@ 3 zbdba staff 96 6 22 2017 win
drwxr-xr-x@ 17 zbdba staff 544 6 22 2017 vio
drwxr-xr-x@ 6 zbdba staff 192 6 22 2017 unittest
drwxr-xr-x@ 6 zbdba staff 192 6 22 2017 testclients
drwxr-xr-x@ 13 zbdba staff 416 6 22 2017 support-files
drwxr-xr-x@ 58 zbdba staff 1856 6 22 2017 strings
drwxr-xr-x@ 9 zbdba staff 288 6 22 2017 sql-common
drwxr-xr-x@ 577 zbdba staff 18464 6 22 2017 sql
drwxr-xr-x@ 20 zbdba staff 640 6 22 2017 scripts
drwxr-xr-x@ 30 zbdba staff 960 6 22 2017 regex
drwxr-xr-x@ 4 zbdba staff 128 6 22 2017 rapid
drwxr-xr-x@ 77 zbdba staff 2464 6 22 2017 man
drwxr-xr-x@ 6 zbdba staff 192 6 22 2017 Docs
drwxr-xr-x@ 13 zbdba staff 416 6 22 2017 storage
drwxr-xr-x@ 3 zbdba staff 96 6 22 2017 boost
drwxr-xr-x 7 zbdba staff 224 6 25 13:11 cmake-build-debugMySQL 的重要目录如表1-1 所示。
表1-1 MySQL 的重要目录
| 目录 | 作用 |
|---|---|
| BUILD | 主要包含一些各平台编译的脚本 |
| boost | boost 库是一个开源的 C++ 库,提供了许多实用的功能和工具,例如字符串和文本处理、数据结构和算法、图像处理、网络编程等,MySQL 强依赖该库 |
| client | 客户端相关逻辑,例如我们使用的MySQL 客户端,在客户端执行一条SQL 语句最终发送到服务器进行处理 |
| cmake | CMake 使用CMakeLists.txt 文件来描述项目的构建配置,包括源文件、依赖项、编译选项等。通过CMake 方便地在不同平台上进行项目的构建和部署,并且可以灵活地配置项目的属性 |
| cmd-line-utils | 客户端依赖的一些工具,例如readline、libedit 工具 |
| Docs | 一些文档,主要是ChangeLog |
| extra | 包含innodbchecksum、lz4_decompress 等工具 |
| include | 包含所有的头文件 |
| libbinlogevents | 包含binlog 所有event 的定义 |
| libevent | 一个用于事件驱动网络编程的库。它提供了一种高效的方式来处理网络连接、I/O 事件、定时器等 |
| libmysql | 库文件,主要供客户端使用 |
| libmysqld | 嵌入式MySQL 服务端库,可以运行在客户端应用侧,它在5.7.19 版本中已经被废弃,在8.0 版本中已经被移除 |
| libservices | 定义了一些函数供动态插件加载使用 |
| man | 包含MySQL 所有命令的使用说明,在安装完MySQL 之后,可以使用man mysql 来查看 |
| mysql-test | 包含MySQL 的所有测试集,如果修改了MySQL 内核的代码,需要运行一下该测试集以确保所有功能正常 |
| mysys | MySQL 封装的常用系统工具方法,并且支持跨平台,例如实现的string、字符集、内存分配等 |
| mysys_ssl | OpenSSL、YaSSL 相关封装函数的实现 |
| package | 用于打包及rpm 构建 |
| plugin | 包含一些插件的实现,例如半同步插件、全文本插件等 |
| rapid | 包含MySQL Group Replication 核心逻辑,就是我们常说的MySQL MGR 集群版 |
| regex | 包含一些正则表达式的实现 |
| scripts | 包含一些脚本,例如mysqld_safe.sh 用来运行mysqld_safe 以启动MySQL 或者初始化MySQL |
| sql | 这里包含MySQL Server 层的大部分实现,连接认证、交互协议、查询优化、主从复制等,MySQL 启动入口文件mysqld.cc 也在这个目录下 |
| sql-common | 存放部分客户端和服务端都会用到的代码 |
| storage | 包含MySQL 所有存储引擎的实现,例如MyISAM、InnoDB、CSV、blackhole 等 |
| string | 包含一些字符串处理的行数 |
| unittest | 包含一些单侧文件 |
| vio | MySQL 实现的虚拟I/O 系统,里面封装了网络I/O 相关的操作函数 |
| zlib | MySQL 实现的压缩算法,用于表压缩、网络传输压缩 |
前面介绍了大概的目录,这里主要根据模块来介绍一些核心文件,方便大家在研究不同的模块的时候快速上手。MySQL 的重要模块及其解释如表1-2 所示。
表1-2 MySQL 的重要模块及其解释
| 模块 | 文件路径及作用 |
|---|---|
| MySQL 启动入口 | sql/mysqld.cc,MySQL 启动入口,在该文件的mysqld_main 方法中 |
| MySQL 客户端实现 | client/mysql.cc,MySQL 客户端启动入口,在该文件的main 方法中 client/mysqldump.c,mysqldump 工具主要逻辑 client/mysqlbinlog.cc,mysqlbinlog 工具主要逻辑 |
| MySQL 服务端连接认证、协议、网络模块 | sql/conn_handler/connection_handler_manager.cc,处理MySQL 连接相关,为客户端创建一个用户线程 sql/protocol_classic.cc,MySQL 协议相关逻辑 sql/net_serv.cc,MySQL 网络相关逻辑 sql/auth/sql_authentication.cc,MySQL 登录认证相关逻辑 |
| 查询优化模块 | sql/sql_optimizer.cc,MySQL 优化器核心逻辑 sql/sql_parse.cc,MySQL 语法解析核心逻辑 sql/sql_planner.cc,MySQL 执行计划核心逻辑 |
| Server 层锁模块 | sql/lock.cc,Server 层锁的实现,例如元数据锁(Metadata Lock,MDL) |
| 日志模块 | sql/log.cc,error log、general log 核心逻辑 |
| 存储过程、函数、触发器、事件 | sql/events.cc,MySQL 事件相关逻辑 sql/sp.cc,MySQL 存储过程核心逻辑 sql/table_trigger_dispatcher.cc,触发器相关逻辑 |
| MGR 模块 | rapid/plugin/group_replication/src/plugin.cc,MGR 核心逻辑 |
| 主从复制模块 | sql/rpl_slave.cc,包含主从复制核心逻辑 |
| 半同步复制模块 | plugin/semisync/semisync_master.cc,半同步核心逻辑 |
| binlog 日志模块 | libbinlogevents/src/binlog_event.cpp,包含binlog 协议核心逻辑 |
| InnoDB 缓冲池 | storage/innobase/include/buf0buf.ic,包含缓冲池核心逻辑 |
| InnoDB 插入缓冲 | storage/innobase/ibuf/ibuf0ibuf.cc,包含插入缓存核心逻辑 |
| InnoDB 自适应哈希索引 | storage/innobase/btr/btr0sea.cc,包含自适应哈希索引核心逻辑 |
| InnoDB 双写机制 | storage/innobase/buf/buf0dblwr.cc,包含双写机制核心逻辑 |
| InnoDB 重做日志 | storage/innobase/log/log0log.cc,包含重做日志读写核心逻辑 storage/innobase/mtr/mtr0log.cc,重做日志mgr 核心逻辑 |
| InnoDB 回滚日志 | storage/innobase/trx/trx0undo.cc,包含回滚日志核心逻辑 |
| InnoDB 数据文件 | storage/innobase/fsp/fsp0file.cc,包含数据文件核心逻辑 |
| InnoDB 锁模块 | storage/innobase/lock/lock0lock.cc,包含InnoDB 锁核心逻辑 |
| InnoDB 事务模块 | storage/innobase/include/trx0sys.ic,包含事务模块核心逻辑 |
| InnoDB 索引模块 | storage/innobase/btr/btr0btr.cc,包含索引核心逻辑 |
| InnoDB 数据字典模块 | storage/innobase/dict/dict0crea.cc,包含数据字典核心逻辑 |
| InnoDB 多版本控制 | storage/innobase/read/read0read.cc,包含MVCC 核心逻辑 |
| InnoDB 后台线程 | storage/innobase/srv/srv0srv.cc,包含master 线程核心逻辑 storage/innobase/srv/srv0start.cc,包含I/O 线程核心逻辑 storage/innobase/buf/buf0flu.cc,包含刷脏页线程逻辑 |
注意,以上文件是MySQL 5.7.19 版本的,其他版本的也基本一致,部分文件可能存在出入。
1.5.2 编译MySQL
MySQL的编译过程对于初学者而言确实具有一定的复杂性,因其涉及诸多参数与依赖项。本小节旨在详尽指导用户完成MySQL的编译过程,并深入解析其中一些至关重要的编译参数,以帮助用户更好地理解与实施。
编译环境:
[root@iZ0jl76srbmwqzii8km11rZ mysql-5.7.19]# uname -a
Linux iZ0jl76srbmwqzii8km11rZ 3.10.0-1160.83.1.el7.x86_64 #1 SMP Wed Jan 25 16:41:43 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
[root@iZ0jl76srbmwqzii8km11rZ mysql-5.7.19]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)安装依赖包:
yum install cmake
yum -y install ncurses-devel
yum install bison
yum -y install gcc-c++TIP
如果下载的MySQL源码不包含boost包,则需要下载。下载地址:https://sourceforge.net/projects/boost/files/boost/1.59.0/boost_1_59_0.zip/download
安装完依赖包之后开始编译,创建一个build目录来存放编译后的文件:
[root@iZ0jl76srbmwqzii8km11rZ mysql-5.7.19]# mkdir build
[root@iZ0jl76srbmwqzii8km11rZ mysql-5.7.19]# cd build/
[root@iZ0jl76srbmwqzii8km11rZ build]#先执行cmake:
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/mysql-5.7.19 \
-DMYSQL_UNIX_ADDR=/tmp/mysql.sock \
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_bin \
-DWITH_EXTRA_CHARSETS:STRING=all \
-DWITH_MYISAM_STORAGE_ENGINE=1 \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_MEMORY_STORAGE_ENGINE=1 \
-DWITH_READLINE=1 \
-DENABLED_LOCAL_INFILE=1 \
-DMYSQL_TCP_PORT=3313 \
-DMYSQL_DATADIR=/data1/mysql3313 \
-DMYSQL_USER=mysql \
-DDOWNLOAD_BOOST=1 \
-DWITH_DEBUG=1 \
-DWITH_BOOST=/data/tmp/mysql-5.7.19/boost执行成功之后会有如下输出:
-- CMAKE_BUILD_TYPE: Debug
-- COMPILE_DEFINITIONS: _GNU_SOURCE;_FILE_OFFSET_BITS=64;HAVE_CONFIG_H;HAVE_LIBEVENT1
-- CMAKE_C_FLAGS: -fPIC -Wall -Wextra -Wformat-security -Wvla -Wwrite-strings -Wdeclaration-after-statement -Werror
-- CMAKE_CXX_FLAGS: -fPIC -Wall -Wextra -Wformat-security -Wvla -Woverloaded-virtual -Wno-unused-parameter -Werror
-- CMAKE_C_LINK_FLAGS:
-- CMAKE_CXX_LINK_FLAGS:
-- CMAKE_C_FLAGS_DEBUG: -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DENABLED_DEBUG_SYNC -DSAFE_MUTEX
-- CMAKE_CXX_FLAGS_DEBUG: -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DENABLED_DEBUG_SYNC -DSAFE_MUTEX
-- Configuring done
-- Generating done
CMake Warning:
Manually-specified variables were not used by the project:
MYSQL_USER
WITH_MEMORY_STORAGE_ENGINE
WITH_READLINE
-- Build files have been written to: /data/tmp/mysql-5.7.19/build然后执行编译:
make如果CPU数量充足,可以指定并行编译:
make -j4编译会执行10多分钟,性能较差的计算机可能需要几十分钟,编译成功之后会有如下输出:
Building CXX object sql/CMakeFiles/sql.dir/sql_client.cc.o
[ 98%] [ 98%] Building CXX object sql/CMakeFiles/sql.dir/srv_session.cc.o
Building CXX object sql/CMakeFiles/sql.dir/srv_session_info_service.cc.o
[ 98%] Building CXX object sql/CMakeFiles/sql.dir/srv_session_service.cc.o
[ 98%] Building CXX object sql/CMakeFiles/sql.dir/mysqld_daemon.cc.o
Linking CXX static library libsql.a
[ 98%] Built target mysqltest_embedded
[ 98%] Built target sql
Scanning dependencies of target pfs_connect_attr-t
Scanning dependencies of target mysqld
[100%] Building CXX object sql/CMakeFiles/mysqld.dir/main.cc.o
Linking CXX executable mysqld
[100%] [100%] Building CXX object storage/perfschema/unittest/CMakeFiles/pfs_connect_attr-t.dir/pfs_connect_attr-t.cc.o
Building CXX object storage/perfschema/unittest/CMakeFiles/pfs_connect_attr-t.dir/__/__/__/sql/sql_builtin.cc.o
[100%] Building C object storage/perfschema/unittest/CMakeFiles/pfs_connect_attr-t.dir/__/__/__/mysys/string.c.o
Linking CXX executable pfs_connect_attr-t
[100%] Built target mysqld
Scanning dependencies of target udf_example
[100%] Building CXX object sql/CMakeFiles/udf_example.dir/udf_example.cc.o
Linking CXX shared module udf_example.so
[100%] Built target udf_example
[100%] Built target pfs_connect_attr-t然后进行安装,这个过程会把编译好的二进制文件安装到指定的目录中:
[root@iZ0jl76srbmwqzii8km11rZ build]# make install安装成功之后有如下信息输出:
-- Installing: /usr/local/mysql-5.7.19/mysql-test/./include/restore_strict_mode.inc
-- Installing: /usr/local/mysql-5.7.19/mysql-test/./include/turn_off_strict_mode.inc
-- Installing: /usr/local/mysql-5.7.19/mysql-test/mtr
-- Installing: /usr/local/mysql-5.7.19/mysql-test/mysql-test-run
-- Installing: /usr/local/mysql-5.7.19/mysql-test/lib/My/SafeProcess/my_safe_process
-- Up-to-date: /usr/local/mysql-5.7.19/mysql-test/lib/My/SafeProcess/my_safe_process
-- Installing: /usr/local/mysql-5.7.19/mysql-test/lib/My/SafeProcess/Base.pm
-- Installing: /usr/local/mysql-5.7.19/support-files/mysqld_multi.server
-- Installing: /usr/local/mysql-5.7.19/support-files/mysql-log-rotate
-- Installing: /usr/local/mysql-5.7.19/support-files/magic
-- Installing: /usr/local/mysql-5.7.19/share/aclocal/mysql.m4
-- Installing: /usr/local/mysql-5.7.19/support-files/mysql.server然后检查一下最终安装好的二进制文件,进入/usr/local/mysql-5.7.19/bin/目录中:
[root@iZ0jl76srbmwqzii8km11rZ bin]# cd /usr/local/mysql-5.7.19/bin/可以看到MySQL相关的二进制文件及命令行工具都在这个目录中,执行MySQL客户端命令工具:
[root@iZ0jl76srbmwqzii8km11rZ bin]# ./mysql --version
./mysql Ver 14.14 Distrib 5.7.19, for Linux (x86_64) using EditLine wrapper到此就编译结束了。下面来重点介绍MySQL的重要编译参数及其说明,如表1-3所示。
表1-3 MySQL的重要编译参数及其说明
| 编译参数 | 说明 |
|---|---|
CMAKE_INSTALL_PREFIX | 指定MySQL二进制文件安装位置 |
MYSQL_UNIX_ADDR | 指定MySQL默认套接字文件位置 |
DEFAULT_CHARSET | 指定MySQL默认字符集 |
WITH_MYISAM_STORAGE_ENGINE | 指定是否支持MyISAM存储引擎 |
WITH_INNOBASE_STORAGE_ENGINE | 指定是否支持InnoDB存储引擎 |
MYSQL_TCP_PORT | 指定MySQL默认端口号 |
MYSQL_DATADIR | 指定MySQL默认数据文件目录 |
MYSQL_USER | 指定MySQL用户 |
WITH_DEBUG | 指定debug模式,注意,这里由于我们是为了调试,所以需要指定,用于生产环境的不需要指定 |
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所示。
图1-4 准备导入MySQL项目
然后弹出Select Directory to Import(选择导入目录)对话框,这里就选择我们刚刚编译完成的build目录,如图1-5所示。
图1-5 选择build目录
选择完后单击OK按钮,然后弹出Import CMake Project对话框,如图1-6所示。
图1-6 弹出Import CMake Project对话框
单击Open Existing Project按钮,最终导入完成。之后CLion会加载一段时间,加载完成后就可以准备配置MySQL启动参数了。单击Edit Configurations选项,如图1-7所示。
图1-7 单击Edit Configurations选项
选择mysqld选项,在右边可以看到一些配置,从这里配置启动参数,如图1-8所示。
图1-8 配置MySQL启动参数截图
在图1-8中的Program arguments文本框中填入对应的启动参数,参数如下:
--defaults-file=/data/mysql3313/my.cnf
--basedir=/data/mysql3313
--datadir=/data/mysql3313/data
--plugin-dir=/usr/local/mysql-5.7.19/lib/plugin
--user=mysql
--log-error=zbdba.err
--pid-file=zbdba.pid
--socket=/var/lib/mysql/mysql_3313.sock
--port=3313WARNING
配置好了还不能直接启动,因为还没有初始化MySQL。
2. 初始化MySQL
首先创建MySQL目录:
[root@iZ0jl76srbmwqzii8km11rZ data]# mkdir mysql3313
[root@iZ0jl76srbmwqzii8km11rZ data]# cd mysql3313/然后创建一个简单的MySQL配置文件:
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# cat my.cnf
[mysqld]
# 数据库存储路径
datadir = /data/mysql3313/data
# 端口号
port = 3313
# 最大连接数
max_connections = 100
# 连接超时时间
connect_timeout = 10
# 等待数据库连接释放的时间
wait_timeout = 600
# 日志文件
log_error = /data/mysql3313/error.log
# 慢查询日志
slow_query_log = 1
slow_query_log_file = /data/mysql3313/slow_query.log
# 查询缓存大小
query_cache_size = 64M
# 表打开缓存大小
table_open_cache = 256
# 线程缓存大小
thread_cache_size = 8
# tmpdir = /data/mysql3313/tmp再创建MySQL的data和tmp目录、错误日志文件:
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# mkdir data
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# mkdir tmp
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# touch /data/mysql3313/error.log
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# ls
data error.log my.cnf slow_query.log tmp创建MySQL用户:
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# useradd mysql
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# groupadd mysql -g mysql授予MySQL目录的mysql用户权限:
[root@iZ0jl76srbmwqzii8km11rZ data]# chown -R mysql:mysql mysql3313/
[root@iZ0jl76srbmwqzii8km11rZ data]# cd mysql3313/
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# ls -lrt
-rw-r--r-- 1 mysql mysql 536 7月 5 15:02 my.cnf
drwxr-xr-x 2 mysql mysql 4096 7月 5 15:04 data
drwxr-xr-x 2 mysql mysql 4096 7月 5 15:04 tmp然后进行初始化:
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# /usr/local/mysql-5.7.19/bin/mysqld_safe --defaults-file=/data/mysql3313/my.cnf --user=mysql --initialize
2024-07-05T07:11:36.586942Z mysqld_safe Logging to '/data/mysql3313/error.log'.
2024-07-05T07:11:36.612018Z mysqld_safe Starting mysqld daemon with databases from /data/mysql3313/data
2024-07-05T07:11:40.617190Z mysqld_safe mysqld from pid file /data/mysql3313/data/iZ0jl76srbmwqzii8km11rZ.pid ended在初始化完成之后查看日志文件,可以看到初始化的MySQL密码:
[root@iZ0jl76srbmwqzii8km11rZ mysql3313]# cat error.log
2024-07-05T07:11:06.619494Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2024-07-05T07:11:37.775855Z 0 [Warning] InnoDB: New log files created, LSN=45790
2024-07-05T07:11:38.026907Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2024-07-05T07:11:38.097652Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: d21a1651-3a9d-11ef-8d68-00163e03689c.
2024-07-05T07:11:38.099752Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2024-07-05T07:11:38.100462Z 1 [Note] A temporary password is generated for root@localhost: 8Uyge+Wekp30
这表明MySQL已经初始化完成,接下来可以通过IDE来启动.在启动之前,我们在IDE上设置一个断点,断点就设置在MySQL的入口函数中,如图1-9所示.
> 图1-9 设置断点
断点的设置非常简单,单击代码最左侧的空白位置即可.然后单击IDE的调试按钮进行调试,调试按钮如图1-10所示.
> 图1-10 调试按钮
断点调试MySQL如图1-11所示.
> 图1-11 断点调试MySQL
可以看到MySQL停在了断点上,这个时候就可以一步步地进行调试了.从这里调试可以看到MySQL的整个启动流程.如果想让MySQL直接启动,则单击CLion的继续按钮,如图1-12所示.
> 图1-12 直接启动MySQL
在IDE中启动了MySQL后,在终端通过MySQL客户端进行连接:
```shell
[root@iZ0jl76srbmwqzii8km11rZ build]# /usr/local/mysql-5.7.19/bin/mysql -uroot -p'8Uyge+Wekp30' -P3313
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.19-debug-log
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>WARNING
这里的密码就是刚刚初始化时生成的临时密码.
若需调试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下多线程调试命令如下:
# 查看所有的线程 info thread # 切换线程 thread 3在CLion等IDE下可以直接通过鼠标进行线程的查看和切换.
-
多线程运行相同的代码.针对多线程运行相同的代码,调试起来就比较麻烦,当你设置一个断点的时候,可能有多个线程触发,这个时候就会干扰你的调试.可以通过在GDB中设置
set scheduler-locking on命令来强制限定只调试当前线程.
2. 调试分布式项目
分布式项目一般会部署多个节点.在处理分布式项目的调试任务时,通常会选取某一节点作为断点调试的焦点.然而,由于分布式系统特有的协议与交互机制,该选定节点有可能面临被剔除或发生异常的风险,从而阻碍我们实施准确调试的进程.针对这一挑战,调试分布式项目主要可遵循以下两大策略:
-
Mock或者单元调试.有些项目是自己做好了Mock,我们只需要调试一个节点就几乎能调试其所有的功能.如果没有Mock,我们也可以尝试自己做Mock,不过这需要你对该项目非常熟悉.除了Mock,我们还可以采用单元测试来对每个函数进行调试.不过这会有逻辑不连贯、单侧覆盖不全的问题.
-
打印日志.分布式项目最常用的方式还是打印日志.在一些重要的地方打印日志,就能知道代码的大致执行路径.在厘清整体执行路径后,再在每个方法中打印详细日志即可完成调试.
TIP
在调试MySQL的MGR(MySQL Group Replication)时,就会遇到上述问题.
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配置如下:
- 打开Linux的core文件配置.
ulimit -c unlimited- 添加MySQL的core_file配置(配置在
[mysqld]下面),并重启测试实例.
[mysqld]
core_file
[mysqld_safe]
core-file-size=unlimited
- 配置suid_dumpable(MySQL通常会以suid方式启动).
echo 2 > /proc/sys/fs/suid_dumpable- 设置core文件存放的目录并设置完全控制权限.
mk dir /data/core && chmod 777 /data/core && echo "/data/core/core" > /proc/sys/kernel/core_pattern22 MySQL内核设计与实现
- 模拟MySQL的crash场景,执行如下命令.
kill -SEGV `pidof mysqld`kill操作执行完成后,终于看到了久违的core文件.
找到core文件后执行:
gdb core.1234 /usr/local/mysql-5.7.19/bin/mysqld5. pstack
pstack是一个功能强大的工具,它具备查看运行中程序所有线程堆栈信息的能力.然而,使用该工具时需要谨慎,因为它在捕获进程堆栈的过程中,会导致进程出现短暂的阻塞现象.特别是在处理拥有大量线程的程序时,这一行为可能会对服务的正常运行产生不良影响.pstack命令如下:
[root@zbdba mysql-5.7.19]# ps -ef|grep mysql
root 10620 5674 0 21:23 pts/2 00:00:00 vim sql/mysqld.cc
root 27575 3841 0 21:32 pts/1 00:00:00 /bin/sh /usr/local/mysql-5.7.19-online/bin/mysqld_safe --defaults-file=/data/mysql3322/my.cnf
mysql 27960 27575 0 21:32 pts/1 00:00:10 /usr/local/mysql-5.7.19-online/bin/mysqld --defaults-file=/data/mysql3322/my.cnf --basedir=/data/mysql3322 --datadir=/data/mysql3322/data --plugin-dir=/usr/local/mysql-5.7.19-online/lib/plugin --user=mysql --log-error=zbdba.err --pid-file=zbdba.pid --socket=/var/lib/mysql/mysql_3322.sock --port=3322
root 72534 3841 0 21:59 pts/1 00:00:00 grep --color=auto mysql
[root@zbdba mysql-5.7.19]#
[root@zbdba mysql-5.7.19]# pstack 27960
Thread 31 (Thread 0x7fdb64948700 (LWP 27967)):
#0 0x00007fdb6d11c53a in sigwaitinfo () from /lib64/libc.so.6
#1 0x0000000000eb865b in timer_notify_thread_func (arg=arg@entry=0x7ffce7e6be50) at /home/zhaojingbo/online/mysql-5.7.19/mysys/posix_timers.c:77
#2 0x0000000001213131 in pfs_spawn_thread (arg=0x3cbb4b0) at /home/zhaojingbo/online/mysql-5.7.19/storage/perfschema/pfs.cc:2188
#3 0x00007fdb6e51ee65 in start_thread () from /lib64/libpthread.so.0
#4 0x00007fdb6d1e388d in clone () from /lib64/libc.so.6
Thread 30 (Thread 0x7fdb1382c700 (LWP 27972)):
#0 0x00007fdb6e315644 in __io_getevents_0_4 () from /lib64/libaio.so.1
#1 0x0000000000f6fee7 in LinuxAIOHandler::collect (this=this@entry=0x7fdb1382bdf0) at /home/zhaojingbo/online/mysql-5.7.19/storage/innobase/os/os0file.cc:2500
#2 0x0000000000f7085a in LinuxAIOHandler::poll (this=this@entry=0x7fdb1382bdf0, m1=m1@entry=0x7fdb1382be90, m2=m2@entry=0x7fdb1382bea0, request=request@entry=0x7fdb1382beb0) at /home/zhaojingbo/online/mysql-5.7.19/storage/innobase/os/os0file.cc:2646
#3 0x0000000000f726d8 in os_aio_linux_handler (request=0x7fdb1382beb0, m2=0x7fdb1382bea0, m1=0x7fdb1382be90, global_segment=0) at /home/zhaojingbo/online/mysql-5.7.19/storage/innobase/os/os0file.cc:2702
#4 os_aio_handler (segment=segment@entry=0, m1=m1@entry=0x7fdb1382be90,
m2=m2@entry=0x7fdb1382bea0, request=request@entry=0x7fdb1382beb0) at /home/zhaojingbo/online/mysql-5.7.19/storage/innobase/os/os0file.cc:6259
#5 0x000000000112a32d in fil_aio_wait (segment=segment@entry=0) at /home/zhaojingbo/online/mysql-5.7.19/storage/innobase/fil/fil0fil.cc:5835
#6 0x000000000101e758 in io_handler_thread (arg=<optimized out>) at /home/zhaojingbo/online/mysql-5.7.19/storage/innobase/srv/srv0start.cc:311
#7 0x00007fdb6e51ee65 in start_thread () from /lib64/libpthread.so.0
#8 0x00007fdb6d1e388d in clone () from /lib64/libc.so.6在MySQL夯住时,或者看MySQL的后台线程时,就会用到上述命令.实际上,通过上述命令就能找到对应的方法,如果想要调试,就可以到对应的方法中加上断点.
6. 调试及阅读代码的思路
总体策略是分层次地阅读,遵循由整体至细节的原则.首先明确调用的基本路径,随后深入核心方法的理解,从而在心中构建出一个清晰的框架体系.对于规模庞大的项目,尤为重要的是采取模块化视角进行审视,细致掌握各模块的功能定位及其实现机制,并在此基础上 有针对性地提出疑问.随后,应带着这些问题深入源代码中探寻答案,以确保对项目有全面且深入的理解.
(1) 熟悉目录及文件
当我们着手探索一个庞大的开源项目时,首要任务是明确其目录结构及各关键文件的基本功能,以此为基础构建初步的认知框架.这些准备工作将有助于我们在后续的代码阅读与调试过程中保持清晰的思路与方向.MySQL的目录结构及重要文件已在前文中详尽阐述,此处不再重复说明.
(2) 熟悉具体功能原理
我们应当全面且系统地收集有价值的资料,以深入理解其实现原理.在审视代码之前,务必确保已充分掌握其背后的逻辑与机制,否则在浏览代码的过程中可能会感到困惑不解.
(3) 先看注释再看代码
在一些优秀的项目中,基本上每个方法都会有注释,并且会对每个入参和出参进行说明,例如MySQL的方法:
/*
从master读取一个事件
SYNOPSIS
read_event()
mysql MySQL连接
mi master连接信息
suppress_warnings 设置为True的时候,当正常的网络读取超时导致我们尝试重新连接时,
我们不想向错误日志中打印任何内容,因为这在空闲服务器中是正常事件。
RETURN VALUES
'packet_error' 错误信息
number 包的长度
*/24 MySQL内核设计与实现
static ulong read_event(MYSQL *mysql, Master_info *mi, bool *suppress_warnings)在看完注释后,我们能大致知道该方法是干什么用的,可以判断要不要继续看下去,如果继续看下去就更容易理解.如果注释没有放在方法前,则可能在头文件定义中.
(4) 跟着主线思路走
在代码调试过程中,我们时常会深陷于代码的执行流程,而偏离了初始的调研目标.这会浪费大量时间,尤其是在面对拥有数百万行代码的庞大项目时.然而,值得注意的是,这些项目的核心逻辑代码量往往远小于整体规模.因此,为了高效推进工作,我们需要聚焦于核心要点,并采用以下策略:
- 持续保持对调研初衷的清晰认知,时刻提醒自己此次代码审查的核心目的.
- 精准定位核心方法,对于诸如检查、校验、条件判断等辅助性方法,仅需理解其基本功用即可,无须深入探究其实现细节.
- 一旦识别出核心实现部分,务必记录相应的堆栈信息.此举旨在为后续可能的断点调试工作提供便利,确保能够迅速定位并专注于关键代码段.
(5) 利用参数和返回值快速阅读代码
有时候一个方法可能有上千行,如果跟着代码的逻辑走,可能会花费大量的时间.这时就可以通过入参、出参和返回值来快速定位核心代码.
大部分方法的主要作用其实就是处理入参,然后里面可能调用其他方法再将该参数或者处理过的参数传入该方法中,最终都要么有返回值,要么写到出参中.我们只需要跟踪这些参数和返回值,即可快速找到核心逻辑.
例如MySQL中的page_delete_rec_list_end:
/*****************************************************//**
从给定记录开始删除页面上的记录,包括该给定记录本身。但最小记录和最大记录不会被删除。*/
void
page_delete_rec_list_end(
/*=====================*/
rec_t* rec,
/* 指向页面上记录的指针。*/
buf_block_t* block,
/*页的缓冲块。*/
dict_index_t* index,
/* 记录描述符。*/
ulint n_recs,
/* !< in: 要删除的记录数量,如果未知则为无符号长整型未定义值(ULINT_UNDEFINED)。*/
ulint size,
/* !< in: 要删除的链末尾记录大小的总和,如果未知则为无符号长整型未定义值(ULINT_UNDEFINED)。*/
mtr_t* mtr) /*!< in: mtr */上述方法是想要删除一条MySQL的记录,可以看到参数里面有一个rec,也就是要删除这条记录,所以我们只需要跟踪rec这条记录即可快速理清楚核心逻辑。
7. 利用单元测试进行调试理解
如果遇到无法直接通过常规手段触发特定代码调试的情况,一个可行的策略是转而利用单元测试来辅助调试过程。
NOTE
MySQL的测试框架因其独特性,并不支持此种调试方式。而部分项目则能够采用单元测试作为调试手段。
8. 查看代码提交记录
在某些情况下,即便经过多次调试,可能仍难以理解特定的逻辑段。这可能是由于该逻辑段较为复杂或特殊,且缺乏必要的注释说明。为了应对这一难题,可以采用git blame命令来追溯该逻辑段的修改历史,并查找对应的提交记录。通常,提交记录会详细说明此次修改的目的和内容,为理解该逻辑段提供有价值的线索。此外,如果运气较好,我们甚至可能找到对应的MySQL的工作日志⑩,它可能包含对该逻辑段的概要说明或详细设计,从而进一步加深我们的理解。
⑩ MySQL的工作日志链接:https://dev.mysql.com/worklog/。
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. 总结笔记
记录笔记是非常重要的,推荐在笔记中记录你阅读、分析、调试的过程,记录你的问
26 MySQL内核设计与实现
题以及你收获到了什么,例如:
- 核心流程。一定要记录其实现的核心流程,这样方便后续看的时候快速理解。
- 调用关系。记录调用关系,方便后续调试定位。
另外,对一些实现细节可以添加代码注释,方便后续理解。
12. 分享和交流
自己的理解有时候不全面,或者有一些误差,跟别人交流后总能查漏补缺或者纠正错误。
1.6 总结
自1979年起至今,MySQL内核拥有长达四十余年的发展历程,深远且持久。在此期间,MySQL内核不仅孕育了众多分支版本,还经历了持续不断的迭代与优化。MySQL内核的版本管理体系极为严谨,并辅以一套完善的版本管理流程,确保了产品的稳定性和可靠性。同时,MySQL内核社区氛围极为活跃,非常欢迎各界人士加入到MySQL的开发行列中来,共同推动MySQL的进步与发展。
在阅读本书时,建议准备一套用于查看MySQL源码的环境。这将使你超越绝大多数MySQL用户,并为你从源码层面深入理解MySQL的实现原理提供强有力的支持。
本章详细阐述了从MySQL源码的下载、编译到使用IDE进行调试的全过程。读者只需遵循此流程,即可顺利启动MySQL的调试工作。然而,读者需要具备一定的C/C++编程基础,以便更顺畅地理解和分析源码。
值得注意的是,MySQL值得注意的是,MySQL 的许多逻辑和代码结构相当复杂,即便掌握了调试技巧,也可能难以迅速领悟其精髓。面对这种情况,我们倡导采用反复调试的策略,带着问题去深入探索,通过数十次的迭代调试,即便是最复杂的模块,其大部分内容也将逐渐变得清晰可解。
调试 MySQL 无疑是一项既复杂又耗时的任务,缺乏明确的目标或浓厚的兴趣往往难以持久。笔者之所以能持之以恒地调试 MySQL 的所有核心逻辑代码长达五年,正是源于对技术的浓厚兴趣,以及工作和写作目标的双重驱动。