第一章 MySQL的历史与架构
# 第一章 MySQL的历史与架构
要深入理解MySQL的架构,结合其历史背景是最好的方式。因此,本章将同时探讨这两个方面。
# MySQL的历史
MySQL的历史可以追溯到1979年,当时蒙蒂·维德纽斯(Monty Widenius)在一家名为TcX的小公司工作,他用BASIC语言编写了一个报表工具,该工具运行在一台4兆赫兹、16KB内存的计算机上。随着时间的推移,这个工具用C语言进行了重写,并移植到Unix系统上运行。那时它还只是一个带有报表前端的底层存储引擎,名为Unireg。
在计算资源匮乏的不利条件下工作,或许凭借着天赋,蒙蒂自然而然地养成了编写高效代码的习惯,也具备了这样的能力。他还培养出了(或者说可能从一开始就具备)一种非凡的洞察力,即便对未来开发的具体细节并不十分清楚,也能知道需要对代码做哪些改进,以便其在未来的开发中发挥作用。
此外,由于TcX是一家规模很小的公司,而蒙蒂是公司所有者之一,他对自己编写的代码走向有很大的话语权。或许有不少程序员具备蒙蒂这样的天赋和能力,但由于种种原因,很少有人能让自己的代码历经20多年仍在不断发展。蒙蒂做到了。
蒙蒂的工作、天赋以及对代码的掌控权,为MySQL的奇迹奠定了基础。
20世纪90年代的某个时候,TcX的客户开始要求为他们的数据提供SQL接口。公司考虑了多种可能性。其中一个方案是将数据加载到商业数据库中,但蒙蒂对其速度并不满意。他尝试借用mSQL的SQL部分代码,并将其与自己的底层存储引擎集成,结果也不理想。随后,这位才华横溢、充满干劲的程序员做出了经典之举:“我受够了别人编写的这些不好用的工具!我要自己写一个!”
于是,1996年5月,MySQL 1.0版本发布给了一小部分用户,同年10月,3.11.1版本面向公众发布。最初的公开发行版仅提供了Solaris系统的二进制文件。一个月后,源代码和Linux二进制文件也发布了。
在接下来的两年里,随着功能逐渐增加,MySQL被移植到了许多其他操作系统上。MySQL最初发布时采用了一种特殊的许可协议,允许那些不将其与自身软件一起重新分发的用户进行商业使用。对于想要将其与产品捆绑销售的用户,可以购买特殊许可。此外,公司还提供商业支持服务,这为TcX带来了一些收入,使得MySQL的进一步开发得以持续,尽管它最初创建的目的已经实现。
在此期间,MySQL发展到了3.22版本。它支持相当一部分SQL语言,拥有一个比人们想象中更为复杂的优化器(很难想象这是由一个人编写的),速度极快且非常稳定。许多人贡献了API,因此开发者几乎可以用任何现有的编程语言编写客户端。然而,它仍然不支持事务、子查询、外键、存储过程和视图。而且锁机制仅在表级别,这在某些情况下会导致其速度大幅下降。一些无法绕过这些限制的程序员仍将其视为玩具,而另一些人则非常乐意放弃Oracle或SQL Server转而使用MySQL,他们愿意在代码中处理这些限制,以换取性能的提升和许可成本的节省。
1999 - 2000年前后,一家名为MySQL AB的独立公司成立了。该公司聘请了几位开发者,并与Sleepycat建立了合作关系,为Berkeley DB数据文件提供SQL接口。由于Berkeley DB具备事务处理能力,这使得MySQL获得了此前缺失的事务支持。在为集成Berkeley DB对代码进行了一些修改后,3.23版本发布了。
尽管MySQL开发者始终未能完全解决Berkeley DB接口的所有问题,Berkeley DB表也一直不太稳定,但这些努力并非毫无意义。结果是,MySQL源代码具备了添加任何类型存储引擎(包括事务性存储引擎)的接口。
2000年4月,在Slashdot的鼓励和赞助下,MySQL添加了主从复制功能。旧的非事务性存储引擎ISAM经过重新开发,以MyISAM的形式发布。在众多改进中,MyISAM现在支持全文搜索功能。2001年底,MySQL与NuSphere短暂合作,试图添加具有行级锁定功能的事务引擎Gemini,但最终以诉讼告终。大约在同一时间,海基·图里(Heikki Tuuri)向MySQL AB提出了集成他自己的存储引擎InnoDB的建议,InnoDB也支持事务和行级锁定。
海基的贡献与此前因Berkeley DB集成工作而优化的新表处理程序接口融合得更加顺畅。MySQL/InnoDB的组合成为了4.0版本,并于2001年10月作为alpha版本发布。到2002年初,MySQL/InnoDB组合已经稳定下来,这让MySQL的发展迈上了一个新台阶。4.0版本最终在2003年3月被宣布为稳定生产版本。
值得一提的是,版本号的变化并非因InnoDB的加入。MySQL开发者一直将InnoDB视为重要的补充,但绝非成功的唯一依赖。在当时,甚至到现在,添加新的存储引擎都不太可能成为版本号变更的理由。事实上,与之前的版本相比,4.0版本并没有增加太多新功能。或许最显著的新增功能是查询缓存,它极大地提高了许多应用程序的性能。从库上的复制代码被重写,使用两个线程:一个用于处理来自主库的网络I/O,另一个用于处理更新。优化器也进行了一些改进。客户端/服务器协议增加了对SSL的支持。
4.1版本于2003年4月作为alpha版本发布,并于2004年6月进入beta阶段。与4.0版本不同,4.1版本进行了许多重大改进。其中最引人注目的或许是子查询功能,这是许多用户期待已久的特性。MyISAM存储引擎增加了空间索引支持,实现了Unicode支持,客户端/服务器协议也有了不少变化,安全性得到提升,能够抵御攻击,并且支持预处理语句。
在4.1版本处于alpha阶段的同时,另一个开发分支也在推进:5.0版本。该版本将添加存储过程、服务器端游标、触发器、视图、XA事务、查询优化器的重大改进以及许多其他功能。之所以决定创建一个单独的开发分支,是因为MySQL开发者认为,如果在为4.1版本添加所有新功能的同时,还要处理存储过程相关的工作,那么4.1版本的稳定将会耗费很长时间。5.0版本最终在2003年12月作为alpha版本发布。这一度造成了一些混乱——有两个处于alpha阶段的分支。最终,4.1版本在2004年10月稳定下来,混乱得以解决。
5.0版本在一年后的2005年10月稳定下来。
5.1版本的第一个alpha版本于2005年11月发布,它带来了许多改进,包括表数据分区、基于行的复制、事件调度器,以及一个标准化的插件API,该API便于集成新的存储引擎和其他插件。
目前,MySQL仍在积极开发中。5.0版本是当前的稳定版本,而5.1版本处于beta阶段,很快也将稳定下来。新功能会被添加到5.2版本中。
# MySQL架构
在很大程度上,MySQL架构难以用正式的定义或规范来描述。最初编写大部分代码时,并非着眼于构建未来的某个大型系统,而是为了解决一些非常具体的问题。然而,这些代码编写得非常出色,且富有洞察力,最终足以组合成一个数据库服务器。
# 核心模块
在本节中,我尝试确定系统中的核心模块。不过,我要先说明一下,这只是对现有情况的一种形式化梳理。MySQL开发者很少从这些角度去思考,他们更倾向于从文件、目录、类、结构和函数的角度去考虑问题。相比听到 “这发生在MyISAM存储引擎层面”,听到 “这发生在mi_open()函数中” 的频率要高得多。MySQL开发者对代码了如指掌,能够从函数、结构和类的层面进行概念性思考。他们可能会觉得本节中的抽象内容没什么用处。然而,对于习惯从模块和管理器角度思考的人来说,这些内容会有所帮助。
对于MySQL,我对 “模块” 这个术语的使用较为宽泛。与通常所说的模块不同,在很多情况下,你无法轻易将其抽离并替换为另一种实现。一个模块的代码可能分散在多个文件中,而且你经常会在同一个文件中看到来自不同模块的代码。旧代码尤其如此,而新代码则更符合模块的模式。因此,在我们的定义中,模块是指在逻辑上以某种方式相关联,并且在服务器中执行特定关键功能的一段代码。
服务器中可以识别出以下模块:
- 服务器初始化模块(Server Initialization Module)
- 连接管理器(Connection Manager)
- 线程管理器(Thread Manager)
- 连接线程(Connection Thread)
- 用户认证模块(User Authentication Module)
- 访问控制模块(Access Control Module)
- 解析器(Parser)
- 命令调度器(Command Dispatcher)
- 查询缓存模块(Query Cache Module)
- 优化器(Optimizer)
- 表管理器(Table Manager)
- 表修改模块(Table Modification Modules)
- 表维护模块(Table Maintenance Module)
- 状态报告模块(Status Reporting Module)
- 抽象存储引擎接口(表处理程序)(Abstracted Storage Engine Interface (Table Handler))
- 存储引擎实现(MyISAM、InnoDB、MEMORY、Berkeley DB)(Storage Engine Implementations (MyISAM, InnoDB, MEMORY, Berkeley DB))
- 日志模块(Logging Module)
- 复制主模块(Replication Master Module)
- 复制从模块(Replication Slave Module)
- 客户端/服务器协议API(Client/Server Protocol API)
- 底层网络I/O API(Low-Level Network I/O API)
- 核心API(Core API)
# 核心模块的交互
当在命令行启动服务器时,初始化模块开始工作。它解析配置文件和命令行参数,分配全局内存缓冲区,初始化全局变量和结构,加载访问控制表,并执行许多其他初始化任务。初始化工作完成后,初始化模块将控制权交给连接管理器,连接管理器开始循环监听客户端的连接请求。
当客户端连接到数据库服务器时,连接管理器执行一些底层网络协议任务,然后将控制权交给线程管理器,线程管理器会提供一个线程来处理该连接(从现在起,这个线程将被称为连接线程)。连接线程可能是新创建的,也可能是从线程缓存中获取并被激活使用。连接线程获得控制权后,首先调用用户认证模块,验证连接用户的凭证,之后客户端就可以发出请求了。
连接线程将请求数据传递给命令调度器。在MySQL代码术语中,有些请求被称为命令,其中一些命令可以由命令调度器直接处理,而更复杂的请求则需要重定向到其他模块。典型的命令可能要求服务器运行查询、更改当前数据库、报告状态、持续发送复制更新的转储、关闭连接或执行其他操作。
在MySQL服务器术语中,客户端请求分为两种类型:查询和命令。查询是指任何需要经过解析器处理的请求,而命令则是指无需调用解析器即可执行的请求。在MySQL内部原理的语境中,我们使用 “查询” 这个术语。因此,在我们的术语中,不仅SELECT
语句,DELETE
或INSERT
语句也被称为查询。我们所说的查询有时也被称为SQL语句。
如果启用了完整查询日志记录功能,命令调度器会在调度之前要求日志模块将查询或命令记录到纯文本日志中。因此,在完整日志记录配置下,所有查询都会被记录下来,即使是语法不正确、永远不会执行并立即返回错误的查询也不例外。
命令调度器通过查询缓存模块将查询转发给解析器。查询缓存模块检查该查询是否属于可缓存类型,以及是否存在先前计算并仍有效的缓存结果。如果命中缓存,此时执行过程将短路,缓存结果会返回给用户,连接线程重新获得控制权,准备处理下一个命令。如果查询缓存模块未命中缓存,查询将进入解析器,解析器会根据查询类型决定如何传递控制权。
从这一点开始,可能继续处理查询的模块有:优化器、表修改模块、表维护模块、复制模块和状态报告模块。SELECT
查询会被转发到优化器;更新、插入、删除以及表创建和模式更改查询会被发送到相应的表修改模块;检查表、修复表、更新键统计信息或对表进行碎片整理的查询会进入表维护模块;与复制相关的查询会被发送到复制模块;状态请求则会被发送到状态报告模块。表修改模块也有多种,包括删除模块(Delete Module)、创建模块(Create Module)、更新模块(Update Module)、插入模块(Insert Module)和修改模块(Alter Module)。
此时,每个从解析器获得控制权的模块都会将查询中涉及的表列表传递给访问控制模块,成功通过验证后再传递给表管理器,表管理器会打开这些表并获取必要的锁。现在,表操作模块准备好继续执行其特定任务,并会向抽象存储引擎模块发出许多请求,以进行诸如插入或更新记录、根据键值检索记录,或者在表级别执行操作(如修复表或更新索引统计信息)等底层操作。
抽象存储引擎模块会通过对象多态性自动将调用转换为特定存储引擎模块的相应方法。换句话说,在处理存储引擎对象时,调用者认为自己处理的是一个抽象对象,而实际上该对象是更具体的类型:它是与给定表类型对应的存储引擎对象。接口方法是虚拟的,这就产生了透明的效果。会调用正确的方法,而调用者无需知道存储引擎对象的确切类型。
在处理查询或命令的过程中,相应的模块会在结果集的部分数据可用时将其发送给客户端,也可能会发送警告或错误消息。如果发出错误消息,客户端和服务器都会明白该查询或命令执行失败,并会采取相应措施。客户端将不再接受针对该查询的任何更多结果集、警告或错误消息数据,而服务器在发出错误消息后,总会将控制权转移回连接线程。需要注意的是,由于MySQL出于实现稳定性和可移植性的考虑不使用异常机制,因此所有层级的调用都必须检查错误,并在失败时进行适当的控制权转移。
如果底层模块以某种方式对数据进行了修改,并且启用了二进制更新日志记录功能,那么该模块将负责要求日志模块将更新事件记录到二进制更新日志中,二进制更新日志有时也被称为复制日志,在MySQL开发者和高级用户中通常简称为binlog。
任务完成后,执行流程返回到连接线程,连接线程进行必要的清理工作,并等待客户端的下一个查询或命令。这个会话会一直持续,直到客户端发出Quit
命令。
除了与普通客户端进行交互,服务器还可能收到来自复制从库的命令,要求持续读取其二进制更新日志。这个命令将由复制主模块处理。
如果服务器被配置为复制从库,初始化模块将调用复制从模块,复制从模块会启动两个线程,分别称为SQL线程和I/O线程。它们负责将主库上发生的更新传播到从库。同一台服务器也可以同时被配置为主库和从库。
与客户端的网络通信通过客户端/服务器协议模块进行,该模块负责将数据打包成合适的格式,并根据连接设置对数据进行压缩。客户端/服务器协议模块进而使用底层网络I/O模块,该模块负责以跨平台的可移植方式在套接字级别发送和接收数据。如果连接选项设置得当,它还负责使用OpenSSL库调用对数据进行加密。
在执行各自任务的过程中,服务器的核心组件高度依赖核心API。核心API提供了丰富的功能集,包括文件I/O、内存管理、字符串操作、各种数据结构和算法的实现,以及许多其他有用的功能。MySQL开发者被鼓励避免直接调用libc函数,而是使用核心API,以便于未来将代码移植到新平台并进行优化。
图1-1展示了核心模块及其交互关系。
图1-1. MySQL模块的高层视图
# 核心模块详解
现在,我们将更深入地研究每个组件。此次讨论的目的之一是将前面提到的概念性内容与实际源代码联系起来。此外,我们还将介绍每个组件的一些发展历程,并尝试推测其未来的发展方向。
我们会频繁提及源代码,你可能会发现,在文本编辑器中打开提到的文件并定位函数引用会很有帮助。这也可以在调试器中完成,如第3章所述。该章还会告诉你如何获取源代码。
# 服务器初始化模块
服务器初始化模块负责在服务器启动时进行初始化工作。大部分代码位于sql/mysqld.cc
文件中。入口点是C/C++程序员所熟知的main()
函数。以下是一些其他值得关注的函数,如果未提及文件位置,则均位于sql/mysqld.cc
中:
init_common_variables()
init_thread_environment()
init_server_components()
grant_init()
,位于sql/sql_acl.cc
中init_slave()
,位于sql/slave.cc
中get_options()
虽然3.22版本中的代码从未彻底重写,但随着MySQL新增功能,它已经过大量重构。曾经在main()
函数中的一大部分初始化代码,在代码的发展过程中逐渐被整理为多个辅助函数。此外,从4.0版本开始,命令行和配置文件选项的解析从GNU的getopt()
函数切换到了MySQL核心API选项解析器。
在5.1版本中,init_server_components()
函数增加了很大一部分用于插件初始化的代码。
总体而言,这部分代码相当稳定。根据过往历史,随着未来新增需要在启动时进行特殊初始化的功能,预计可能会有一些增量式的添加。不过,这部分代码不太可能被重写。
# 连接管理器
连接管理器监听来自客户端的传入连接,并将请求分发给线程管理器。这个模块实际上只是sql/mysqld.cc
中的一个函数:handle_connections_sockets()
。然而,由于它在服务器运行中起着关键作用,因此值得将其归类为一个独立模块。大量的#ifdef
指令表明,将网络代码移植到各种操作系统上是一项颇具挑战的工作。
随着时间的推移,代码进行了一些演变,以适应不同操作系统网络系统调用中的特殊情况。未来,在尝试新的移植或者不同操作系统厂商在新版本产品中引入新的特殊情况时,可能还需要进一步的更改。
# 线程管理器
线程管理器负责跟踪线程,并确保分配一个线程来处理客户端的连接。这是另一个非常小的模块。大部分代码位于sql/mysqld.cc
中。入口点是create_new_thread()
。另一个值得关注的函数是start_cached_thread()
,也在同一文件中定义。
或许可以将在sql/sql_class.h
中定义、在sql/sql_class.cc
中实现的THD
类视为这个模块的一部分。THD
类型的对象是线程描述符,在大多数服务器模块的运行中至关重要。许多函数都将THD
指针作为第一个参数。
在3.23版本添加线程缓存时,线程管理代码进行了重大修改。从那以后,它就没有太大变化了。可以合理预期,未来它也不会有重大改动。
然而,如果从抽象层面将THD
类本身视为这个模块的一部分,那么就变化而言,情况就有所不同了。随着预处理语句、服务器端游标和存储过程等新功能的添加,在4.1和5.0版本中,THD
类进行了重大改写。现在,它是Query_arena
、Statement
、Security_context
和Open_tables_state
类的超类,这些类也在sql/sql_class.h
中定义。
# 连接线程
连接线程是在已建立的连接上处理客户端请求的核心工作模块。这个模块同样很小,仅由sql/sql_parse.cc
中的一个函数handle_one_connection()
组成。尽管它规模不大,但鉴于其在服务器中的作用,仍值得归类为一个模块。
随着时间的推移,代码不断演变,随着各种涉及THD
变量的初始化操作被移至THD
类中,代码逐渐变得更加紧凑和易读。可以合理预期,未来这段代码不会有太大变化。
# 用户认证模块
用户认证模块对连接的用户进行身份验证,并初始化包含用户权限信息的结构和变量。这个模块的入口点是sql/sql_parse.cc
中的check_connection()
函数。然而,其余功能代码位于sql/sql_acl.cc
和sql/password.cc
中。一些值得研究的函数包括:
acl_check_host()
,位于sql/sql_acl.cc
中create_random_string()
,位于sql/password.cc
中check_user()
,位于sql/sql_parse.cc
中acl_getroot()
,位于sql/sql_acl.cc
中
该代码仅在4.1版本中进行过一次重大改写。由于这些更改可能产生的影响,MySQL开发者在尝试更新实现更安全认证所需的协议之前,等待了一段时间。
从那以后,这段代码变化不大。然而,随着5.1版本中插件功能的添加,MySQL开发者计划添加可插拔认证和角色功能,这将需要对这段代码进行修改。
# 访问控制模块
访问控制模块用于验证客户端用户是否拥有足够的权限来执行请求的操作。大部分代码位于sql/sql_acl.cc
中。然而,最常用的函数之一check_access()
位于sql/sql_parse.cc
中。其他一些值得关注的函数(除非另有说明,均位于sql/sql_acl.cc
中)如下:
check_grant()
check_table_access()
,位于sql/sql_parse.cc
中check_grant_column()
acl_get()
自3.22版本以来,代码本身变化不大。不过,在4.0版本中添加了新的权限类型,这在一定程度上改变了代码其他部分使用这个模块的方式。MySQL开发者计划增加对角色的支持,这将需要对这个模块进行重大修改。
# 解析器
解析器负责解析查询语句并生成解析树。入口点是sql/sql_parse.cc
中的mysql_parse()
函数,它会进行一些初始化操作,然后调用yyparse()
函数,yyparse()
是由GNU Bison从sql/sql_yacc.yy
生成的函数,sql/sql_yacc.yy
包含MySQL所支持的SQL语言子集的定义。需要注意的是,与许多开源项目不同,MySQL有自己生成的词法扫描器,而不是使用lex
。第9章将详细讨论MySQL词法扫描器。除了上述提到的文件外,一些值得关注的文件包括:
sql/gen_lex_hash.cc
sql/lex.h
sql/lex_symbol.h
sql/lex_hash.h
(生成文件)sql/sql_lex.h
sql/sql_lex.cc
sql/
目录下以item_
开头、扩展名为.h
或.cc
的一组文件
随着新的SQL功能不断添加,解析器也在不断变化以适应这些变化。然而,解析器的核心结构相当稳定,到目前为止,它能够适应功能的增长。可以合理预期,虽然会添加一些新元素,但在一段时间内,其核心部分不会有太大变化。MySQL开发者一直(有时仍在)讨论对解析器进行核心重写,并将其从yacc/Bison
切换出去以提高速度。然而,他们已经讨论了至少七年,这仍未成为优先事项。
# 命令调度器
命令调度器负责将请求导向能够处理它们的底层模块。它由sql/sql_parse.cc
中的两个函数do_command()
和dispatch_command()
组成。
随着支持的命令集不断增加,这个模块也在不断发展。预计未来会有小幅度的增长,但核心结构不太可能改变。
# 查询缓存模块
查询缓存模块用于缓存查询结果,并尽可能通过返回缓存结果来缩短查询的执行过程。它在sql/sql_cache.cc
中实现。一些值得关注的方法包括:
Query_cache::store_query()
Query_cache::send_result_to_client()
该模块于4.0版本添加。预计未来除了修复漏洞外,不会有太多变化。
# 优化器
优化器负责制定最佳策略来处理查询,并执行该策略以将结果返回给客户端。它可能是MySQL代码中最复杂的模块。入口点是sql/sql_select.cc
中的mysql_select()
函数。第9章将详细讨论这个模块。其他一些值得关注的函数和方法(均位于sql/sql_select.cc
中)包括:
JOIN::prepare()
JOIN::optimize()
JOIN::exec()
make_join_statistics()
find_best_combination()
optimize_cond()
深入研究优化器时,有一个值得关注的部分,即范围优化器。它与优化器核心有足够的独立性,且复杂性较高,因此被单独分离到sql/opt_range.cc
文件中。范围优化器负责优化使用给定值范围或一组范围的键的查询。范围优化器的入口点是SQL_SELECT::test_quick_select()
。
优化器一直处于不断变化的状态。4.1版本中添加的子查询增加了另一层复杂性。5.0版本增加了对最优表连接顺序的贪心搜索功能,以及每个表使用多个键(索引合并)的能力。可以合理预期,未来还会有更多的变化。一个备受期待的改进是子查询优化方面的提升。
# 表管理器
表管理器负责创建、读取和修改表定义文件(扩展名为.frm
),维护一个称为表缓存的表描述符缓存,并管理表级锁。大部分代码位于sql/sql_base.cc
、sql/table.cc
、sql/unireg.cc
和sql/lock.cc
中。第9章将详细讨论这个模块。一些值得关注的函数包括:
openfrm()
,位于sql/table.cc
中mysql_create_frm()
,位于sql/unireg.cc
中open_table()
,位于sql/sql_base.cc
中open_tables()
,位于sql/sql_base.cc
中open_ltable()
,位于sql/sql_base.cc
中mysql_lock_table()
,位于sql/lock.cc
中
自3.22版本以来,除了4.1版本中引入的新表定义文件格式外,这段代码变化不大。过去,Monty曾对表缓存代码的低效表示不满,并希望重写它。有一段时间,这并非首要任务。不过,在5.1版本中,最终还是取得了一些进展。
# 表修改模块
这组模块负责诸如创建、删除、重命名、删除、更新或插入表等操作。实际上,这是相当大的一部分代码。遗憾的是,由于篇幅限制,本书不会详细介绍。不过,一旦你熟悉了其余代码,从以下入口点开始阅读源代码并使用调试器,应该能够不太困难地弄清楚具体细节:
mysql_update()
和mysql_multi_update()
,位于sql/sql_update.cc
中mysql_insert()
,位于sql/sql_insert.cc
中mysql_create_table()
,位于sql/sql_table.cc
中mysql_alter_table()
,位于sql/sql_table.cc
中mysql_rm_table()
,位于sql/sql_table.cc
中mysql_delete()
,位于sql/sql_delete.cc
中
在4.0版本中,随着多表更新和删除功能的添加,更新和删除模块有了重大变化。在4.1版本中,为了支持预处理语句,以及在5.1版本中为了支持触发器,更新、插入和删除模块也进行了一些重新组织。除此之外,除了偶尔的一些小改进外,它们变化不大。可以合理预期,未来大部分代码将保持现状。
# 表维护模块
表维护模块负责表的维护操作,如检查、修复、备份、恢复、优化(碎片整理)和分析(更新键分布统计信息)。代码位于sql/sql_table.cc
中。核心函数是mysql_admin_table()
,还有以下便捷包装函数:
mysql_check_table()
mysql_repair_table()
mysql_backup_table()
mysql_restore_table()
mysql_optimize_table()
mysql_analyze_table()
mysql_admin_table()
会进一步将请求分发给相应的存储引擎方法。大部分工作在存储引擎层面完成。
这个模块于3.23版本引入,用于为表维护提供SQL接口。在此之前,表维护必须离线进行。在4.1版本中,网络协议模块进行了重大更改以支持预处理语句,这影响了所有与客户端交互的模块,包括表维护模块。除此之外,自引入以来,它变化不大,可以合理预期未来也不会有太大变化。
# 状态报告模块
状态报告模块负责解答有关服务器配置设置、性能跟踪变量、表结构信息、复制进度、表缓存状况等方面的查询。它处理以SHOW
开头的查询。大部分代码位于sql/sql_show.cc
文件中。一些值得关注的函数(除非另有说明,均位于sql/sql_show.cc
文件中)包括:
mysqld_list_processes()
mysqld_show()
mysqld_show_create()
mysqld_show_fields()
mysqld_show_open_tables()
mysqld_show_warnings()
show_master_info()
(位于sql/slave.cc
文件中)show_binlog_info()
(位于sql/sql_repl.cc
文件中)
该模块一直在不断发展。新功能的添加产生了对额外状态报告的需求。可以合理预计,这种模式在未来仍将持续。
# 抽象存储引擎接口(表处理器)
这个模块实际上是一个名为handler
的抽象类和一个叫做handlerton
的结构体。handlerton
结构体是在5.1版本中为插件集成而添加的。它提供了一个标准化接口,用于执行底层存储和检索操作。
表处理器在sql/handler.h
中定义,部分在sql/handler.cc
中实现。派生的特定存储引擎类必须实现这个类的所有纯虚方法。第9章将对其进行更详细的讨论。
该模块于3.23版本引入,目的是为了便于Berkeley DB表的集成。这一举措产生了深远的影响:现在,各种底层存储引擎都能较为轻松地集成到MySQL中。在集成InnoDB时,代码得到了进一步优化。该模块的未来在很大程度上取决于有哪些新的存储引擎将被集成到MySQL中,以及现有存储引擎会如何变化。例如,有时某些底层存储引擎中的新特性可能需要对抽象接口进行扩展,以便上层模块能够使用。
# 存储引擎实现(MyISAM、InnoDB、MEMORY、Berkeley DB)
每个存储引擎都通过扩展前面提到的handler
类,为其操作提供一个标准接口。派生类的方法根据特定存储引擎的底层调用定义了标准接口操作。这一过程以及各个存储引擎将在第10章详细讨论。同时,作为快速入门,你可能需要查看一些相关的文件和目录:
sql/ha_myisam.h
sql/ha_myisam.cc
sql/ha_innodb.h
sql/ha_innodb.cc
sql/ha_heap.h
sql/ha_heap.cc
sql/ha_ndbcluster.h
sql/ha_ndbcluster.cc
myisam/
innobase/
heap/
ndb/
当存储引擎接口首次被抽象化(3.23版本)时,只有三个功能完备的存储引擎:MyISAM、ISAM(MyISAM的旧版本)和MEMORY。(注意,MEMORY存储引擎以前被称为HEAP,源代码树中的一些文件和目录名仍然反映了这个旧名称。)然而,随着BerkeleyDB、MERGE、InnoDB以及最近用于MySQL集群的NDB的加入,存储引擎的种类迅速增加。大多数存储引擎仍在积极开发中,未来我们可能会看到更多新的存储引擎被添加进来。
# 日志模块
日志模块负责维护更高级别的(逻辑)日志。存储引擎可能会出于自身目的额外维护其自己的低级(物理或逻辑)日志,但日志模块不会关注这些;存储引擎自行负责管理。目前的逻辑日志包括二进制更新日志(主要用于复制,否则用于其他用途)、命令日志(主要用于服务器监控和应用程序调试)和慢查询日志(用于追踪未优化的查询)。
在5.1版本之前,该模块大部分由MYSQL_LOG
类实现,这个类在sql/sql_class.h
中定义,在sql/log.cc
中实现。5.1版本对该模块进行了重写。现在存在一个日志管理类的层次结构,MYSQL_LOG
是TC_LOG
的超类,它们都在sql/log.h
中定义。
然而,日志记录的大部分工作发生在二进制复制日志中。用于创建和读取二进制复制日志事件的类在sql/log_event.h
中定义,在sql/log_event.cc
中实现。复制主模块和复制从模块都在很大程度上依赖于日志模块的这一功能。
随着复制功能的引入,该模块发生了重大变化。5.0版本为支持XA事务进行了一些修改。5.1版本增加了像查询SQL表一样搜索日志的功能,这需要对代码进行重大重构。二进制日志部分为了适应基于行的复制也进行了重大修改。目前很难预测这段代码未来的发展方向。
# 复制主模块
复制主模块负责主服务器上的复制功能。该模块最常见的操作是根据请求向从服务器持续发送复制日志事件。大部分代码位于sql/sql_repl.cc
文件中。核心函数是mysql_binlog_send()
。
该模块于3.23版本添加,除了将代码块整理为函数进行全面清理外,没有经历过任何重大变化。一开始,代码对故障安全复制有非常宏伟的开发计划。然而,在这些计划实现之前,MySQL从爱立信收购了NDB集群代码,并开始寻求另一条实现自动故障转移最终目标的途径。鉴于这些发展,目前尚不清楚原生MySQL复制将如何发展。
第12章将对该模块进行更详细的讨论。
# 复制从模块
复制从模块负责从服务器的复制功能。从服务器的作用是从主服务器获取更新,并在从服务器上应用这些更新。从4.0版本开始,从服务器是双线程的。网络I/O线程从主服务器请求并接收持续的更新流,并将它们记录在本地中继日志中。SQL线程从中继日志中读取更新并应用它们。该模块的代码位于sql/slave.cc
文件中。需要研究的最重要的函数是handle_slave_io()
和handle_slave_sql()
。
该模块与复制主模块一起于3.23版本添加。在4.0版本中,当单一的从线程被拆分为SQL线程和I/O线程时,它经历了重大变化。
第12章将对该模块进行更详细的讨论。
# 客户端/服务器协议API
MySQL客户端/服务器通信协议位于协议栈中操作系统协议(TCP/IP或本地套接字)之上。这个模块实现了服务器用于创建、读取、解释和发送协议数据包的API。代码位于sql/protocol.cc
、sql/protocol.h
和sql/net_serv.cc
文件中。
sql/protocol.h
和sql/protocol.cc
文件定义并实现了一个类的层次结构。Protocol
是基类,Protocol_simple
、Protocol_prep
和Protocol_cursor
是从它派生出来的。该模块中一些值得关注的函数包括:
my_net_read()
(位于sql/net_serv.cc
文件中)my_net_write()
(位于sql/net_serv.cc
文件中)net_store_data()
(位于sql/protocol.cc
文件中)send_ok()
(位于sql/protocol.cc
文件中)send_error()
(位于sql/protocol.cc
文件中)
在4.0版本中,协议进行了更改,以支持最大4GB大小的数据包。在此之前,限制为24MB。4.1版本添加了Protocol
类层次结构,以处理预处理语句。目前看来,这个级别的协议中大多数有问题的地方都已得到解决,可以合理预计,这段代码在近期不会有太大变化。不过,MySQL开发者正在考虑添加对通知的支持。
第5章将对该模块进行更详细的讨论。
# 低级网络I/O API
低级网络I/O API为低级网络I/O和SSL会话提供了抽象。代码位于vio/
目录中。该模块中的所有函数名称都以vio_
开头。
该模块于3.23版本引入,是为了满足支持SSL连接的需求。对低级网络I/O进行抽象也便于将MySQL移植到新平台,并维护旧版本的端口。
# 核心API
核心API堪称MySQL的“瑞士军刀”。它提供了可移植的文件I/O、内存管理、字符串操作、文件系统导航、格式化打印功能,还有丰富的数据结构和算法,以及许多其他功能。如果出现问题,通常在核心API模块中都能找到解决方案。如果没有,也会专门编写代码来解决。在很大程度上,这个模块体现了Monty解决问题的能力和决心,他从不会只解决一个问题。它或许是MySQL奇迹的核心组件。
代码位于mysys/
和strings/
目录中。许多核心API函数的名称都以my_
开头。
该模块一直处于发展和改进的状态。在添加新功能时,开发者会格外注意保持其稳定性和高性能。可以合理预计,这种模式在未来仍将继续。
第3章将对该模块进行更详细的讨论。