CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • MySQL开发与调试指南 说明
  • 第一章 MySQL的历史与架构
  • 第二章 使用MySQL源代码的基础操作
    • Unix Shell
    • BitKeeper
    • 为从BitKeeper代码库构建MySQL准备系统
    • 从BitKeeper代码库构建MySQL
    • 从源代码发行版构建
    • 将MySQL安装到系统目录
    • 源代码目录结构
    • 准备在调试器中运行MySQL的系统环境
    • 使用调试器探索源代码
    • 使用gdb的基础知识
    • 在源代码中查找内容
    • 有趣的断点和变量
    • 进行源代码修改
    • 编码规范
      • 稳定性
      • 可移植性
      • 性能
      • 风格和易于集成
    • 保持你的BitKeeper代码库更新
    • 提交补丁
  • 第三章 核心类、结构、变量和API
  • 第四章 客户端/服务器通信
  • 第五章 配置变量
  • 第六章 基于线程的请求处理
  • 第七章 存储引擎接口
  • 第八章 并发访问与锁机制
  • 第九章 解析器与优化器
  • 第十章 存储引擎
  • 第十一章 事务
  • 第十二章 复制
目录

第二章 使用MySQL源代码的基础操作

# 第二章 使用MySQL源代码的基础操作

通过研究MySQL的源代码,我们能学到很多知识。MySQL的创始人Monty Widenius曾半开玩笑地说,源代码是终极文档。确实,假设硬件和编译器都能正常工作,软件就会完全按照源代码的指令运行。然而,理解像MySQL这样复杂程序的源代码颇具挑战性。本章旨在帮助你在研究源代码时抢占先机。

# Unix Shell

尽管MySQL能在多种不同平台上运行,但如果你在类似Unix的系统(如Linux、FreeBSD、Mac OS X或Solaris)上拥有一个账户,研究其源代码会更轻松。如果你一开始没有偏好,我推荐使用Linux,它既可以是远程服务器,也可以在你的桌面电脑上运行。本章中的示例假设你已登录到Unix命令行界面,并且你的shell在某种程度上与Bourne shell兼容。获得这样一个shell的一种方法是在登录后立即执行:

/bin/sh
1

# BitKeeper

MySQL开发者使用BitKeeper(http://www.bitmover.com)进行源代码版本控制。一个包含MySQL源代码的BitKeeper代码库对外公开,可进行只读访问。虽然也可以通过下载压缩归档文件获取MySQL源代码,但使用BitKeeper有诸多优势:

  • 你能获取最新的源代码版本,并能每天跟进所有开发动态。
  • BitKeeper工具能让你轻松跟踪代码变更。
  • 你可以轻松记录自己的更改,并向MySQL开发者提交补丁。

不幸的是,使用BitKeeper也存在一些缺点:

  • 如果你要下载修订历史记录,初始设置需要下载超过30MB的数据。
  • 为了构建MySQL,必须安装诸如autoconf、automake和bison等特定工具。
  • 由于BitMover决定终止开放日志许可证,在没有购买商业许可证的情况下,无法自动集成你的更改、提交补丁或执行其他任务。

如果在你的情况下,使用BitKeeper的缺点超过了优点,请参考本章中“从源代码发行版构建”部分的内容。否则,第一步是确保你的系统上已安装BitKeeper。

在没有商业许可证的情况下,使用BitKeeper的唯一优势是能够获取最新的开发源代码。如果你不打算使用BitKeeper的商业版本,请按照以下说明下载免费的BitKeeper客户端:

  1. 下载http://www.bitmover.com/bk-client.shar。
  2. 运行/bin/sh bk-client.shar解压文件。
  3. 执行cd bk_client-1.1; make进行构建。
  4. 设置PATH=$PWD:$PATH,以解决sfioball中的一些问题。要获取MySQL源代码,请执行:
$ sfioball bk://mysql.bkbits.net/mysql-version some-directory
1

其中version是你感兴趣的MySQL版本号,如5.1,some-directory是你想要存放源代码的目录。例如,你可以输入:

$ sfioball bk://mysql.bkbits.net/mysql-5.1 /home/devel/mysql-5.1
1

如果你想要获取完整的修订历史记录(这会使下载时间更长),请输入:

$ sfioball -r+ bk://mysql.bkbits.net/mysql-5.1 /home/devel/mysql-5.1
1

要更新你的版本(假设你当前的版本是5.1),请执行:

$ update bk://mysql.bkbits.net/mysql-5.1 /home/devel/mysql-5.1
1

你还可以在http://mysql.bkbits.net上在线浏览不同MySQL版本树的变更集。

如果你愿意购买BitKeeper的商业版本,请访问http://www.bitmover.com/cgi-bin/license.cgi,并按照说明操作,你需要填写一份表格,然后会收到一封电子邮件,其中包含下载详情。

安装好BitKeeper后,我建议运行bk help tool,以便熟悉以下基本命令:

  • bk clone
  • bk edit
  • bk new
  • bk rm
  • bk citool
  • bk commit
  • bk pull
  • bk push
  • bk diffs

熟悉BitKeeper之后,下一步是克隆你想要研究的MySQL版本的代码库。在撰写本文时,有六个可用的版本代码库,总结如下表2 - 1所示。 表2 - 1:通过BitKeeper维护的MySQL版本

MySQL版本 描述 BitKeeper代码库URL
3.23 历史悠久且不再受支持的版本 bk://mysql.bkbits.net/mysql-3.23
4.0 旧版本,不再受支持 bk://mysql.bkbits.net/mysql-4.0
4.1 旧的受支持版本,即将逐步淘汰 bk://mysql.bkbits.net/mysql-4.1
5.0 当前稳定版本 bk://mysql.bkbits.net/mysql-5.0
5.1 当前测试版本 bk://mysql.bkbits.net/mysql-5.1
5.2 当前开发版本 bk://mysql.bkbits.net/mysql-5.2

以下说明和讨论主要适用于BitKeeper的商业版本。

要创建代码库的本地副本,请执行克隆命令:

$ bk clone url
1

例如,如果你想要获取5.1版本代码库的副本,请输入:

$ bk clone bk://mysql.bkbits.net/mysql-5.1
1

要克隆代码库或通过sfioball访问它,你本地的BitKeeper实例必须连接到mysql.bkbits.net的7000端口。如果你位于严格的防火墙之后,防火墙可能会阻止该端口的出站连接。幸运的是,如果你有本地HTTP代理(在第一个命令中替换正确的主机名和端口),商业版本中有一个解决方法:

$ http_proxy="http://proxy_host_name:proxy_port/"
$ export http_proxy
1
2

如果你没有商业版本,也可以通过创造性的隧道和端口转发来克服这一限制。

初始克隆操作将传输超过30MB的数据,所以根据你的连接速度和整体网络拥塞情况,这可能需要一些时间。我在美国东部时间晚上11点左右,使用犹他州普罗沃市640 MBit/s的DSL连接进行上述克隆操作时,整个过程耗时9分钟。

克隆完成后,你会在当前目录下看到一个与代码库名称对应的子目录。例如,如果你克隆的是5.1版本,目录名称将是mysql-5.1。如果你想要一个不同的名称,可以在原始命令中添加一个参数,例如:

$ bk clone bk://mysql.bkbits.net/mysql-5.1 src/mysql
1

# 为从BitKeeper代码库构建MySQL准备系统

克隆完BitKeeper代码库后,必须安装以下工具,构建脚本才能正常工作:

  • autoconf
  • automake
  • m4
  • libtool
  • GNU make
  • bison
  • gcc或其他C++编译器

2003年下半年或之后发布的大多数Linux发行版中都包含所需版本的这些工具。如果你使用的是较旧或高度定制的Linux安装,或者你使用的是其他Unix变体,请参考表2 - 2,以验证你是否拥有每个工具的所需版本。 表2 - 2:所需构建工具的版本

工具 最低要求版本 URL
autoconf 2.53 http://www.gnu.org/software/autoconf/
automake 1.8 http://www.gnu.org/software/automake/
m4 无版本限制 http://www.gnu.org/software/m4/
libtool 1.5 http://www.gnu.org/software/libtool/
GNU make 3.79 http://www.gnu.org/software/make/
bison 1.75 http://www.gnu.org/software/bison/
gcc 2.95 http://www.gnu.org/software/gcc/

请注意,每个工具的可执行二进制文件必须在你的PATH路径中。以下Bourne shell脚本将提供一个有用的摘要,你可以用它来评估你的系统是否准备就绪:

#!   /bin/sh
for tool  in  autoconf automake m4  libtool make   bison gcc do
    echo  "Checking for  $tool:" $tool  –version
done
1
2
3
4

如果工具已安装且在你的路径中,该脚本会生成类似以下的输出:

Checking for autoconf:
autoconf  (GNU  Autoconf)  2.59
Written  by  David  J .  MacKenzie  and  Akim  Demaille .
Copyright  (C)  2003  Free  Software  Foundation,  Inc.
This  is free  software;  see the  source for  copying  conditions.   There  is  NO   warranty;  not  even for MERCHANTABILITY or   FITNESS  FOR  A  PARTICULAR  PURPOSE . 
Checking for  automake:
automake  (GNU automake)  1.8.5
Written  by  Tom  Tromey  <tromey@redhat.com> .
Copyright  2004  Free  Software  Foundation,  Inc.
This  is free  software;  see the  source for  copying  conditions.   There  is  NO   warranty;  not  even for MERCHANTABILITY or   FITNESS  FOR  A  PARTICULAR  PURPOSE . 
Checking for m4:
GNU m4  1.4o
Checking for  libtool:
ltmain.sh  (GNU  libtool)  1.5.6  (1.1220.2.94  2004/04/10  16:27:27)
Copyright  (C)  2003    Free  Software  Foundation,  Inc.
This  is free  software;  see the  source for  copying  conditions.   There  is  NO   warranty;  not  even for MERCHANTABILITY or   FITNESS  FOR  A  PARTICULAR  PURPOSE . 
Checking for make:
GNU  Make  version  3.79.1,  by  Richard  Stallman  and  Roland  McGrath . Built for  i686-pc-linux-gnu
Copyright  (C)  1988,  89,  90,  91,  92,  93,  94,  95, 96, 97, 98, 99,  2000 Free  Software  Foundation,  Inc.
This  is free  software;  see the  source for  copying  conditions.
There  is  NO  warranty;  not  even for MERCHANTABILITY or  FITNESS  FOR A PARTICULAR  PURPOSE .
Report  bugs  to  <bug-make@gnu.org> .
Checking for  bison:
bison  (GNU  Bison)  1.875
Written  by  Robert  Corbett  and  Richard  Stallman .
Copyright  (C)  2002  Free  Software  Foundation,  Inc.
This  is free  software;  see the  source for  copying  conditions.   There  is  NO   warranty;  not  even for MERCHANTABILITY or   FITNESS  FOR  A  PARTICULAR  PURPOSE . 
Checking for gcc:
2.95.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

从脚本的输出中确定每个实用工具的版本,并与表格中的版本进行比较。如果版本号低于要求的版本号,请进行必要的升级。

# 从BitKeeper代码库构建MySQL

MySQL开发者创建了许多脚本,用于在不同平台上构建不同类型的MySQL二进制文件。这些脚本位于BitKeeper代码库的BUILD目录中。遗憾的是,在撰写本文时,我没有找到能在任何架构上构建启用调试功能二进制文件的脚本。为了解决这个问题,我将提供创建此类脚本的说明:

  1. 将compile-pentium-debug复制为compile-generic-debug。
  2. 在文本编辑器中打开compile-generic-debug。
  3. 将extra_flags="$pentium_cflags $debug_cflags"这一行改为extra_flags="-g $debug_cflags"。
  4. 删除所有以extra_configs=开头的行。
  5. 在. "$path/FINISH.sh"这一行之前添加一行extra_configs=""。
  6. 保存编辑后的文件。

编辑后,compile-generic-debug看起来如下:

#!  /bin/sh
path='dirname  $0'
.  "$path/SETUP.sh"  $@  --with-debug=full
extra_flags="-g  $debug_cflags"
c_warnings="$c_warnings  $debug_extra_warnings"
cxx_warnings="$cxx_warnings  $debug_extra_warnings" 
extra_configs=""

.  "$path/FINISH.sh"
1
2
3
4
5
6
7
8
9

现在你可以使用compile-generic-debug进行构建了。在克隆代码库的根目录下的shell提示符处,执行以下命令:

$  BUILD/compile-generic-debug
1

该脚本将生成make文件,创建包含定义的必要头文件,然后编译MySQL服务器、客户端和其他各种实用工具。这是一个相当耗时的过程。在我的桌面电脑(速龙2200+,1.5GB内存,Linux系统)上,这花费了12分钟。

在构建过程中,你可能会看到参与该过程的不同工具发出的许多警告。如果构建没有中止,通常可以忽略这些警告。要验证构建是否成功,请在shell提示符处输入:

$ make test
1

理想情况下,你会看到所有测试要么通过,要么被跳过。由于服务器关闭和重启存在困难,某些测试(尤其是测试复制功能的测试)在某些系统上可能会失败。由于BitKeeper代码库可能包含发布版本之间的源代码,有时开发人员可能会提交尚未修复的错误的测试用例。因此,如果有几个测试失败,对于研究MySQL工作原理的人来说,这不必担心。如果大多数测试通过,那么至少就学习目的而言,可以认为构建的二进制文件是可用的。

这些测试大约需要20分钟才能运行完。如果你不想等那么久,也可以简单检查一下是否创建了mysqld二进制文件:

$ ls  -l sql/mysqld
1

如果构建成功,该命令将生成类似以下的输出:

-rwxr-xr-x     1  sasha        sasha         5001261  Jul  29  12:23  sql/mysqld
1

如果从公共BitKeeper代码库的未修改克隆版本成功构建出二进制文件,测试套件在95%的情况下也会成功。

# 从源代码发行版构建

尽管建议你使用BitKeeper代码库,但在某些情况下,你可能希望使用其他方法来构建MySQL。在这种情况下,你可以使用源代码发行版。虽然在大多数情况下,你只需要gcc、gdb和GNU make,但有时“为从BitKeeper代码库构建MySQL准备系统”一节中提到的其他工具也是必要的。例如,如果你要更改解析器,就需要Bison;如果要向源代码中添加另一个文件,则需要使用autoconf、automake和m4。因此,仍然建议你按照该节中概述的相同步骤,尽可能充分地准备你的系统。

此外,你还需要tar(http://www.gnu.org/software/tar)和gzip(http://www.gnu.org/software/gzip)实用工具来解压归档文件。如果你已经安装了非GNU版本的tar,建议你仍然安装GNU tar。MySQL是使用GNU tar进行归档的,一些tar的变体与它不兼容。

以下说明解释了如何使用源代码发行版下载和编译MySQL:

  1. 参考本章“BitKeeper”部分列出MySQL版本的表格,确定你想要使用的MySQL版本。步骤2 - 5假设你选择了4.1版本。如果你选择了其他版本,则需要对以下步骤进行适当修改,将4.1替换为你选择的版本。
  2. 访问http://dev.mysql.com/downloads/mysql/4.1.html(注意URL中的版本号),向下滚动到页面底部,在“Source downloads”处,点击“Tarball”行上的“Pick a mirror”链接。
  3. 你可以选择填写下一页顶部的表格并提交,或者直接向下滚动到页面底部,选择离你最近的镜像站点,点击链接,然后开始下载。你将下载大约19MB的数据。
  4. 在Unix shell中,切换到你计划存放MySQL源代码的目录,并执行以下命令:
$ gunzip  -d  downloaded-archive-name  |  tar xvf  - 
$  cd mysql-full-version-name
1
2
  1. 按照“从BitKeeper代码库构建MySQL”部分中的说明进行操作。如果你没有安装从BitKeeper代码库构建所需的所有工具,还需要编辑BUILD/FINISH.sh,在以下行的开头添加#进行注释:
aclocal       ||   (echo  \"Can't  execute aclocal\"        &&  exit  1)
autoheader  ||   (echo  \"Can't execute autoheader\"   &&  exit  1)
aclocal       ||   (echo  \"Can't  execute aclocal\"        &&  exit  1)
automake      ||   (echo  \"Can't  execute  automake\"       &&  exit  1)
autoconf      ||   (echo  \"Can't execute autoconf\"       && exit  1)
(cd  bdb/dist &&  sh  s_all)
(cd  innobase  &&  aclocal  &&  autoheader  &&  aclocal  &&  automake  &&  autoconf) 
if  [  -d gemini  ]
then
(cd  gemini  &&  aclocal  &&  autoheader  &&  aclocal  &&  automake  &&  autoconf) 
fi
1
2
3
4
5
6
7
8
9
10
11

# 将MySQL安装到系统目录

如果需要,你可以以root用户身份执行以下命令,将MySQL安装到系统目录:

$ make install
1

默认情况下,安装前缀是/usr/local。你可以通过在构建脚本的extra_configs变量中添加–prefix=/path/to/other-prefix来更改安装前缀。如果你在系统上没有root权限,另一个构建配置选项会很有帮助:在extra_configs中添加--with-mysqld-user=your_user_name。在源代码树的根目录下执行以下命令,可以获取完整的构建配置选项列表:

#  ./configure –help
1

如果你不打算在这个系统上部署你构建的MySQL服务器二进制文件,那么将其安装到系统目录就没有必要了。mysql-test-run脚本允许你启动并测试你在创建目录中的构建二进制文件。

# 源代码目录结构

表2-3列出了MySQL源代码树中的顶级子目录,并对每个目录进行了简要说明。请注意,未来版本中可能会进行一些重新组织,但大部分结构应该相当稳定。 表2-3 MySQL源代码树中的顶级目录

子目录 描述
BUILD 开发者构建脚本。
Build-tools 主要是用于构建二进制发行版的脚本。
Docs 文档。
NEW-RPMS 发行版构建脚本用于存放新RPM包的目录。
SSL 早期SSL开发的一些配置文件。
VC++Files 用于在Windows上构建MySQL二进制文件。
bdb Berkeley DB存储引擎代码。Berkeley DB支持事务和页级锁。然而,Berkeley DB与MySQL核心之间的接口开发得不是很好,当需要事务支持时,InnoDB存储引擎是更好的选择。在5.1版本中已移除。
client 命令行客户端实用工具代码。
cmd-line-utils 用于增强命令行客户端的外部库(libedit和readline)。
dbug 调试库。我个人不太喜欢使用它,因为它会改变程序执行过程,掩盖与时间相关的错误,但包括Monty在内的一些开发者喜欢用它来打印执行跟踪信息。要启用它,需在构建脚本的extra_configs中添加–with-debug。
extra 各种杂项工具的代码。
heap 内存表的代码。在5.1版本中移至storage/目录。
isam ISAM存储引擎代码(已被MYISAM取代,在5.0版本中移除)。
include 头文件目录。
innobase InnoDB存储引擎的代码,支持事务和行级锁。在5.1版本中移至storage目录。
libmysql 用于与服务器进行交互的客户端库代码。
libmysql_r 线程安全版本的客户端库。
libmysqld 用于在独立(嵌入式)模式下使用MySQL服务器功能,而无需连接到服务器的库。
man Unix系统的手册页。
merge 支持ISAM-MERGE表的代码,它允许将几个结构相同的ISAM表当作一个表使用。在5.1版本中移除。
myisam MyISAM存储引擎代码。MyISAM是MySQL原始存储引擎的最新版本。它不支持事务,需要表级锁,但与支持事务的InnoDB存储引擎相比,它占用的磁盘空间更少,在一些查询上速度更快。在5.1版本中移至storage目录。
表2-3 MySQL源代码树中的顶级目录(续)
子目录 描述
--- ---
myisammrg 支持MyISAM-MERGE表的代码,它允许将几个结构相同的MyISAM表当作一个表使用。在5.1版本中移至storage目录。
mysql-test 回归测试套件。
mysys 核心可移植性/辅助API代码。
ndb MySQL Cluster代码、实用脚本和文档。在5.1版本中移至storage目录。
netware 用于Netware系统移植的文件。
os2 用于OS/2系统移植的文件。
pstack pstack库的代码,该库允许可执行文件展开并解析自身的堆栈。在段错误信号处理程序中很有用。
regex 正则表达式库的代码,它使程序员能够操作正则表达式。
scripts 用于多种不同目的的实用脚本集合。当开发者编写了一个脚本但不知道该将其放在何处时,通常就会把它放在这个目录下。不过要注意,这里也是mysqld_safe脚本的所在之处,它可是所有脚本中的“王者”,用于从命令行启动服务器。
sql 用C++编写的核心服务器代码的总目录。其中包括解析器和优化器、抽象表处理程序(存储引擎)接口、复制功能、查询缓存、表锁管理器、读写表定义的代码、日志记录代码以及其他许多部分。这里也有一些零散的C文件,它们在其他目录中找不到合适的位置。同时,这里也是mysqld.cc文件的所在目录,该文件定义了main()函数,服务器启动时从这里开始执行。
sql-bench 用于SQL基准测试的脚本。
sql-common 部分同时用于客户端和服务器代码的文件。
strings 为满足MySQL需求而定制的字符串库。
storage 存储引擎代码目录。在5.1版本中添加。
support-files 各种示例配置文件和实用脚本。还包含用于软件包构建的配置文件,如RPM规范文件。
tests 专门用于测试难以重现的错误的测试用例,这些测试用例不符合标准回归测试套件的格式。
tools 在5.1版本之前,这个目录包含mysqlmanager,这是一个用于对服务器进行受控启动和关闭以及测试复制功能的实用工具。在5.1版本中移除。
unittest 核心API的单元测试。
vio 底层可移植网络I/O代码。
zlib ZLIB压缩库的代码。

# 准备在调试器中运行MySQL的系统环境

为了充分深入学习MySQL的内部机制,并能够运行本章后续部分的示例,你必须在系统上安装gdb(http://www.gnu.org/software/gdb/),并将其添加到系统路径中。你还需要安装X Window System,包括像xterm这样的终端程序。X Window System有许多标准实现,其中最受欢迎的可能是X.org(http://www.x.org)。

上述工具在大多数Linux发行版中默认会预装。然而,为了确保能够在gdb中调试多线程程序,务必确认/lib/libpthread.so和/lib/libthread_db.so这两个库没有被剥离调试信息。下面的示例展示了如何检查:

$ file -L /lib/libthread_db.so
/lib/libthread_db.so: ELF 32-bit LSB shared object, Intel 80386, version 1, not stripped
$ file -L /lib/libpthread.so
/lib/libpthread.so: ELF 32-bit LSB shared object, Intel 80386, version 1, not stripped
1
2
3
4

从输出中可以看到,这两个库都未被剥离调试信息。如果你不幸遇到默认情况下它们被剥离了调试信息,并且找不到适用于你发行版的未剥离版本的软件包,那么可以通过重新编译glibc来解决这个问题。

# 使用调试器探索源代码

现在,完成了这些繁琐但必要的准备工作后,你就可以真正开始探索源代码了。当面对大量不熟悉的代码时,我发现通过在调试器中运行一个非常简单的测试用例来入手特别有帮助。MySQL作为一个多线程服务器,在这方面存在一些困难。幸运的是,MySQL开发者创建了一组工具来方便他们自己进行调试,并且也向公众开放了这些工具。在本节中,你将学习如何使用它们。

在调试器中运行一个简单查询的步骤如下:

  1. 切换到源代码树中的mysql-test子目录。
  2. 创建一个新文件,命名为t/example.test。该文件必须位于t子目录下,扩展名为.test,并且不能与t子目录中已有的测试文件同名。在满足这些限制的前提下,文件名可以任意取。不过,如果你选择了不同的文件名,那么在本示例的其余部分中,你也必须相应地更改对它的引用。
  3. 在编辑的文件中输入以下内容:select 1;
  4. 保存文件。
  5. 执行以下命令创建主结果文件:
$ ./mysql-test-run --local --record example
1
  1. 在一个单独的xterm窗口中执行以下命令,将MySQL服务器加载到gdb中(如果你通过SSH在另一台计算机上运行,确保启用了SSH X转发功能。如果无法启用,例如你使用的是Windows系统,那么使用--manual-gdb代替--gdb):
$ ./mysql-test-run --gdb example
1
  1. 一个xterm窗口将会打开,里面有gdb提示符。MySQL服务器将在sql/sql_parse.cc文件中的mysql_parse()函数处设置一个预设断点后启动。mysql-test-run脚本会启动一个客户端,该客户端将连接到正在被调试的服务器,并开始执行example.test中列出的查询,在我们的例子中就是select 1。参考本章后面的“使用gdb的基础知识”和“有趣的断点和变量”部分,设置你感兴趣的断点,然后在gdb提示符下输入c继续执行。
  2. 当example.test的执行结束时,mysql-test-run返回。然而,调试器窗口将保持打开状态。你可以使用MySQL命令行客户端连接到9306端口,并手动发出各种查询,在调试器中设置断点,并检查它们的执行情况。以下是一些示例。 从Unix shell中输入:
$ ../client/mysql -uroot –host=127.0.0.1 –port=9306 test
1

你将进入MySQL命令行客户端 shell,接着输入:

$ create table t1(n int);
1

当调试器在mysql_parse()处中断时,在调试器窗口中输入:

disa 1
b mysql_insert c
1
2

在MySQL命令行客户端提示符下,输入:

insert into t1 values(345);
1

调试器将在mysql_insert()处中断。在调试器窗口中,输入:

bt
1

调试器将显示当前断点处的堆栈跟踪信息。 9. 完成这次使用调试器探索源代码后,如果你没有gdb提示符,按Ctrl-C;在调试器中执行quit命令,当提示是否要停止正在运行的程序时确认,然后返回到执行mysql-test-run的shell提示符。 10. 为了加快下一次使用调试器探索源代码的执行速度,在shell提示符下执行以下命令进行清理:

$ rm -f var/run/*.pid
1

# 使用gdb的基础知识

gdb有一个类似于Unix shell的命令行界面。你输入一个命令,然后按回车键执行它。如果你以前从未使用过gdb,可以先执行help命令,它会产生以下输出:

命令类别列表:
aliases  -- 其他命令的别名
breakpoints  -- 使程序在特定点停止
data  -- 检查数据
files  -- 指定和检查文件
internals  -- 维护命令
obscure  -- 晦涩的功能
running  -- 运行程序
stack  -- 检查堆栈
status  -- 状态查询
support  -- 支持工具
tracepoints  -- 在不停止程序的情况下跟踪程序执行
user-defined  -- 用户定义的命令
输入“help”加上类别名可获取该类别中的命令列表。输入“help”加上命令名可获取该命令的完整文档。
如果命令名缩写不会引起歧义,则允许使用缩写形式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

上述输出中的说明为你提供了一个起点,你可以在此基础上更深入地学习gdb。例如,如果你想了解如何运行正在被调试的程序,可以执行help running,它会给出以下内容:

运行程序。命令列表:
advance  -- 继续执行程序直到到达给定位置(与break命令的参数格式相同)
attach  -- 附加到GDB外部的进程或文件
continue  -- 继续调试程序
detach  -- 分离先前附加的进程或文件
disconnect  -- 断开与目标的连接
finish  -- 执行到选定的堆栈帧返回
handle  -- 指定如何处理信号
info handle  -- 当程序收到各种信号时调试器的操作
interrupt  -- 中断被调试程序的执行
jump  -- 从指定的行或地址继续调试程序
kill  -- 终止被调试程序的执行
next  -- 单步执行程序(不进入子例程调用)
nexti  -- 单步执行一条指令
run  -- 启动被调试程序
set args  -- 设置启动被调试程序时传递给它的参数列表
set environment  -- 设置传递给程序的环境变量值
set follow-fork-mode  -- 设置调试器对程序调用fork或vfork的响应
set scheduler-locking  -- 设置执行期间锁定调度程序的模式
set step-mode  -- 设置单步操作的模式
show args  -- 显示启动被调试程序时传递给它的参数列表
show follow-fork-mode  -- 显示调试器对程序调用fork或vfork的响应
show scheduler-locking  -- 显示执行期间锁定调度程序的模式
show step-mode  -- 显示单步操作的模式
signal  -- 继续执行程序,并向其发送指定的信号
step  -- 单步执行程序直到到达不同的源代码行
stepi  -- 精确单步执行一条指令
target  -- 连接到目标机器或进程
thread  -- 使用此命令在不同线程之间切换
thread apply  -- 将命令应用于线程列表
apply all  -- 将命令应用于所有线程
tty  -- 设置未来运行被调试程序时使用的终端
unset environment  -- 取消程序的环境变量VAR
until  -- 执行直到程序到达比当前行更大的源代码行
输入“help”加上命令名可获取该命令的完整文档。如果命令名缩写不会引起歧义,则允许使用缩写形式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

现在你有了一个与运行程序相关的命令列表可供学习。假设你想了解run命令,可以执行help run,会产生以下输出:

启动被调试程序。你可以指定传递给它的参数。参数中可以包含“*”或“[...]”,它们会使用“sh”进行扩展。
也允许使用“>”、“<”或“>>”进行输入和输出重定向。
如果不指定参数,则使用上次指定的参数(使用“run”或“set args”指定)。
要取消先前的参数并在不传递参数的情况下运行程序,请使用不带参数的“set args”。
1
2
3
4

使用上述方法,你可以获取gdb命令的完整列表并了解每个命令。如果你更喜欢阅读手册,可以访问http:😕/www.gnu.org/software/gdb/documentation。

表2-4列出了在研究MySQL源代码时可能特别有用的一些命令。请注意“常用缩写”列,即使你打字速度很快,使用缩写形式也能显著加快操作速度。 表2-4 常用gdb命令

命令 常用缩写 描述 示例
breakpoint b 设置断点。 b mysql_select
b sql_parse.cc:245
continue c 继续执行到下一个断点,直到程序终止。如果给出参数n,并且从断点继续执行,则仅在第n次通过该断点时停止。 c
c 10
next n 执行下一行,不进入子例程调用。如果给出参数n,则执行n次,或直到程序因其他原因停止。 n
n 15
step s 执行下一行,进入子例程调用。如果给出参数n,则执行n次,或直到程序因其他原因停止。 s
s 20
finish
backtrace
fin
bt
执行到当前子例程返回。
打印堆栈跟踪信息。如果使用full参数,还会打印每个堆栈帧中的局部变量值。
fin
bt
bt full
enable
disable
ena
disa
启用已禁用的断点。
禁用已启用的断点。
ena 3
disa 3
表2-4 常用gdb命令(续)
命令 常用缩写 描述 示例
--- --- --- ---
info
breakpoints
info line
ib
ili
打印断点列表,并详细说明每个断点的信息。
如果省略参数,则打印正在调试的当前行的信息;否则,打印指定行的信息。
ib
ili
ili main
ili mi_open.c:83
print p 打印作为参数指定的变量或表达式的值。 p thd
p *thd
p thd->query
p length
list l 根据参数的值打印源代码。 l mysql_query
l sql_parse.cc:245
info local
up
ilo
up
显示当前帧的局部变量值。
选择并打印当前帧上面一层帧的局部变量值。参数指定向上的帧数,默认值为1。
info local
up
up 2
down down 选择并打印当前帧下面一层帧的局部变量值。参数指定向下的帧数,默认值为1。 down
down 3
info threads ith 列出所有正在运行的线程,并显示每个线程的一些信息。 ith
thread
info
registers
disassemble
thr
ir
disas
切换到指定的线程。
打印处理器寄存器的内容。
反汇编指定的代码段。如果未给出参数,则反汇编当前堆栈帧的pc寄存器周围的可执行代码。
thr 10
ir
disas
disas mysql_select

# 在源代码中查找内容

在处理一个庞大且陌生的代码库时,程序员常问的一个问题是:“get_and_lock_X()函数到底定义在哪里?” 有很多方法可以找到答案,而且许多程序员都有自己偏爱的方法。对于那些没有特定方法,或者难以将自己的方法应用于MySQL源代码的人,我来分享一下我的方法。

假设你需要查找mysql_lock_tables()函数的定义,可按以下步骤操作:

  1. 按照 “调试器引导的源代码浏览” 一节中所述,在调试器中启动一个MySQL服务器实例。
  2. 调试器窗口打开后,在调试器窗口中输入:i li mysql_lock_tables

调试器会回应:

"lock.cc"文件的第86行
起始地址为0x8125540 <mysql_lock_tables FP3THDPP8st_tableUi>,结束地址为0x812554f <mysql_lock_tables FP3THDPP8st_tableUi+15>。
1
2

这表明mysql_lock_tables()函数定义在lock.cc文件的第86行。遗憾的是,它没有说明该文件所在的目录,所以还需要再执行一步操作。 3. 在源代码树的根目录下执行以下命令:

$ find .  -name  lock.cc  |  xargs  ls  -l
1

该命令的输出会返回两个文件名:

lrwxrwxrwx     1  sasha       sasha                 16  Jul  29  15:08  ./libmysqld/lock.cc  ->  ./ . ./sql/lock.cc
-rw-rw-r--     1  sasha       sasha            20863  Jul  14  21:40  ./sql/lock.cc
1
2

从输出中可以看出,libmysqld/lock.cc只是一个符号链接,实际的文件是sql/lock.cc。

有时情况并不像前面的例子那么顺利。看似是一个函数,实际上可能是一个预处理宏。如果出现这种情况,调试器会告诉你它找不到该符号。幸运的是,grep命令可以派上用场。假设你需要查找ha_commit()的定义,并且调试器已经告知你没有这个符号。在源代码树的根目录下执行以下命令:

$ find  .  -name  \*.h   |  xargs  grep  ha_commit
1

返回的输出如下:

./sql/handler.h:513:#define  ha_commit_stmt(thd) (ha_commit_trans((thd), &((thd)->transaction.stmt)))
./sql/handler.h:515:#define  ha_commit(thd)  (ha_commit_trans((thd), &((thd)->transaction.all)))
./sql/handler.h:543:int  ha_commit_complete(THD  *thd);
./sql/handler.h:545:int  ha_commit_trans(THD  *thd, THD_TRANS  *trans);
./sql/mysql_priv.h:859:extern  ulong  ha_commit_count, ha_rollback_count,table_cache_size;
1
2
3
4
5

从输出中可以看到,宏ha_commit()定义在sql/handler.h文件的第515行,它是ha_commit_trans()的别名,并带有一个额外的参数。

# 有趣的断点和变量

如果你曾经处理过一个规模较大且不熟悉的代码库,肯定会遇到在脑海中梳理执行流程的挑战。没错,我理解这个init_X()函数,但当我执行操作Y时,真正关键的部分到底从哪里开始呢?

表2-5以及受其内容启发的调试器引导的源代码浏览,有望帮助你回答许多类似的问题。它基于4.1版本的源代码,但在很大程度上也适用于其他版本。尽管MySQL开发者可能会在未来版本中随时更改函数名称和代码结构,但实际上,一旦代码的某个部分稳定下来,95% 的函数会保留其原始名称和作用。最后一列中的星号并不表示C++指针,而是表示 “所有名称匹配的变量”。

操作 适合设置入口断点的位置 值得查看的有趣变量
SELECT查询 mysql_select() *thd
thd->query
*tables
*join
INSERT查询 mysql_insert() *thd
thd->query
*table
fields
values_list
UPDATE查询 mysql_update() *thd
thd->query
*table_list
fields
values
*conds
DELETE查询 mysql_delete() *thd
thd->query
*table_list
*conds
检查查询是否可从查询缓存中获取答案 Query_cache::send_result_to_client() *this
*thd
sql
从客户端读取通信数据包 my_net_read() *net
向客户端写入通信数据包 my_net_write() *net
packet len
连接认证 check_connection() *thd
thd->net
在复制主库上记录更新 MYSQL_LOG::write(Log_event *) *event_info
*event_info->thd
event_info->thd-> query
复制从库启动 start_slave_threads() *mi
复制从库上处理网络I/O的复制线程执行 handle_slave_io() *mi
复制从库上处理SQL命令的复制线程执行 handle_slave_sql() *rli
打开表 open_table() *thd
thd->query
db
table_name
alias
读取表定义文件(.frm)
打开MyISAM表
openfrm()
ha_myisam::open()
name
alias
*this
name
打开InnoDB表 ha_innobase::open() *this
name
获取表锁 mysql_lock_tables() *thd
thd->query
**tables
提交事务 ha_commit_trans() *thd
*trans

# 进行源代码修改

如果你没有向源代码中添加任何额外文件,在完成修改后,只需在源代码树的根目录下执行:

$ make
1

然后等待重新编译和链接完成即可。如果你只修改了sql目录下的文件,那么仅在该目录中运行make命令就足够了,这会缩短处理时间,因为make命令不会检查其他目录是否有需要处理的内容。

如果你添加了新文件,请按照以下步骤操作:

  1. 对于添加文件的每个目录,编辑该目录下的Makefile.am文件。
  2. 在每个Makefile.am文件中,找到以SOURCES结尾的相应变量。例如,在sql目录中,该变量名为mysqld_SOURCES,在myisam目录中,变量名为libmyisam_a_SOURCES。
  3. 将你添加的C/C++文件的名称添加到SOURCES变量中。
  4. 在每个Makefile.am文件中,找到INCLUDES和noinst_HEADERS变量。将你希望通过make install命令安装的新头文件的名称添加到INCLUDES变量中,将不需要安装的新头文件的名称添加到noinst_HEADERS变量中。
  5. 按照 “从BitKeeper代码库构建MySQL” 一节中所述,执行BUILD/compile-generic-debug(或与之等效的命令)。

# 编码规范

如果你要对MySQL进行修改,建议你遵循与MySQL开发者相同的编码规范。这将使你的代码更易于与现有代码协同工作,帮助你避免错误,并增加你的补丁在无需大幅修改的情况下被MySQL开发者接受的可能性。

MySQL开发者在http://dev.mysql.com/doc/internals/en/index.html上公开了他们的编码规范。

此外,我将根据自己的经验,对这些规范进行重新整理并添加一些额外的提示和注释。

# 稳定性

以下是在进行修改时保持代码稳定性的一些规范:

  • 始终牢记,你大多时候处于某个线程中,必须遵循线程安全编程的规则。
  • 大多数全局变量都有一个关联的互斥锁(mutex),其他线程在访问该变量之前必须先锁定它。务必了解每个全局变量关联的是哪个互斥锁,并在访问该变量时锁定它。
  • 要注意可用的栈空间非常有限。大于100字节左右的内存块应该使用sql_alloc()或my_malloc()进行分配。
  • 对于小内存分配,尽可能选择sql_alloc()而不是my_malloc()。sql_alloc()从预先分配的连接内存池中分配内存,而my_malloc()只是对常规malloc()调用的封装。sql_alloc()可以在do_command()以下的执行栈中的任何位置调用。要验证相关的栈位置,可以在调试器中在该位置设置断点,断点触发时运行bt命令。请注意,使用sql_alloc()分配的内存会一直持续到查询执行结束。如果你希望分配的内存持续到查询结束之后,应使用my_malloc()。
  • 使用my_malloc()进行大内存分配,并尽快使用my_free()释放已分配的内存块。
  • 不要释放用sql_alloc()分配的指针。查询结束时,调用free_root()会一次性释放内存池。
  • 如果指针是用my_malloc()分配的,使用my_free()释放它。
  • 不要使用异常。只要有可能,代码在编译时都禁用了异常。
  • 不要使用STL、iostream或任何其他需要链接到libstdc++的C++扩展。
  • 尽可能避免引入对其他外部库的依赖。
  • 尽量复用现有的MySQL代码。

# 可移植性

遵循以下建议可以增加你的代码在你编写和测试所用系统之外的其他系统上正常工作的可能性:

  • 不要直接调用libc函数。相反,应使用mysys和strings中的可移植性包装函数。通常,mysys中的包装函数名称与libc函数名称相同,只是前缀为my_,例如my_open()、my_close()、my_malloc()、my_free()。
  • 注意不同系统之间的字节序差异。如果在一个系统上创建的数据字符串可能会以某种方式传输到另一个系统或在多个系统之间共享,应使用int4store()和int4korr()等宏。
  • 注意对齐问题。不要将整数值赋给可能未对齐的指针。例如,不要这样写:*((char*)p+1) = n,而应写成:memcpy((char*)p+1,&n, 4),或者为了实现与机器无关的字节序,可以写成:int4store((char*)p+1,n)。
  • 当引入可能在其他系统上不受支持的特定于系统或编译器的优化时,务必将其封装在#ifdef块中,并在该优化不可用时提供替代方案。
  • 即使某些C编译器支持,也不要在C文件中使用C++风格的注释。
  • 如果你需要控制变量的字节大小,应使用include/my_global.h中预定义的类型(例如uint8、uint32)。

# 性能

以下建议可以帮助你最大限度地优化代码对内存和处理器的使用:

  • 养成自然地从性能角度思考并据此编写代码的习惯。了解CPU的基本工作原理,并想象当你的C/C++代码执行时,在汇编层面会发生什么。
  • 尽可能复用现有的MySQL代码。
  • 清楚你所调用的函数内部的执行情况。
  • 避免不必要的系统调用。思考如何将多个系统调用合并为一个,例如,使用IO_CACHE函数和宏,而不是my_read()和my_write()。
  • 避免不必要的对象实例化。
  • 不要将大型函数声明为内联函数。

# 风格和易于集成

以下是MySQL开发者为保持一致性而制定的一些常规约定:

  • 遵循internals.html中的缩进、变量命名和注释规范。
  • 不要重新格式化你未编写的代码。确保你的编辑器没有配置为自动进行此类操作。
  • 在函数中,成功时返回0,失败时返回非零值。
  • 尽可能使用以下语法调用多个函数,一旦某个函数失败就直接跳出:
if  (a()  ||  b()  ||  c( )) goto err;
1
  • 使用TRUE和FALSE,而不是true和false。
  • 在C源文件中使用my_bool,在C++源文件中使用bool。
  • 在C++代码中,通过指针而不是引用传递参数。
  • 即使在性能要求不高的部分,也要编写优化后的代码。

# 保持你的BitKeeper代码库更新

MySQL源代码会随着时间不断变化 —— 在alpha阶段变化频繁,beta阶段变化较少,到了稳定阶段则只有很少的改动。无论你是在学习MySQL源代码还是对其进行修改,都建议你紧跟最新的开发动态。

以下是更新本地BitKeeper代码库的操作说明。本节内容适用于商业版的BitKeeper。

  1. 如果你之前没有提交过更改,编辑BitKeeper/triggers/post-commit文件,并将其内容替换为:
#!  /bin/sh 
exit 0
1
2

这个脚本会在每次你提交更改时执行,其原始版本会将你的更改详情通知给MySQL开发者和公众。如果是MySQL开发者提交更改,这种通知是有必要的,但对你来说可能并非如此。如果你确实希望每次提交更改时都通知大家,那么可以保留这个脚本的原始内容。 2. 在Unix shell提示符下,在代码库目录内的任意位置执行:

$  bk citool &
1

BitKeeper会花几分钟检查已更改的文件,然后会弹出一个图形用户界面(GUI)对话框,要求你对所做的每一项更改进行注释。 3. 对每个单独文件的更改以及整个更改集都添加注释后,点击两次 “Commit” 按钮,然后等待BitKeeper窗口消失。 4. 在Unix shell提示符下,在代码库目录内的任意位置执行以下命令:

$  bk  pull
1
  1. 在一些极少数情况下,pull操作可能会导致冲突。这通常发生在你和某个MySQL开发者同时修改了相同的代码行时。此时,BitKeeper会在标准输出中打印关于冲突失败的消息,并指示你运行bk resolve命令。照做并按照BitKeeper给出的提示操作。如果需要,可以通过运行bk helptool命令参考BitKeeper的文档。

# 提交补丁

如果你添加了新功能或修复了一个漏洞,并希望MySQL开发者考虑接受你的提交,请按照本节中的步骤操作。这些说明假设你使用的是商业版的BitKeeper。如果你没有使用商业版,那么在第1步中,你需要将你的源代码与未修改的副本进行差异比较。

  1. 在源代码树内的目录中执行以下命令:
$  bk  -r diffs  -c > /tmp/mysql-patch.diff
1
  1. 检查/tmp/mysql-patch.diff的内容,确保这个补丁对你来说是合理的。
  2. 向internals@lists.mysql.com发送邮件。如果补丁内容较少(几千字节以下),可以将其包含在邮件正文中。否则,将补丁发布到一个URL,并在邮件正文中附上该URL。务必在邮件中简要描述一下这个补丁。
上次更新: 2025/04/08, 19:40:35
第一章 MySQL的历史与架构
第三章 核心类、结构、变量和API

← 第一章 MySQL的历史与架构 第三章 核心类、结构、变量和API→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式