mysqld核心函数调用流程解析
2021-09-222.3k 阅读
MariaDB 中 mysqld 进程概述
在 MariaDB 数据库系统里,mysqld
是核心进程,负责管理数据库文件、处理客户端连接、执行 SQL 语句等关键任务。mysqld
启动后,会初始化一系列关键组件,如内存分配模块、日志系统、存储引擎等,为数据库的正常运行奠定基础。
从操作系统层面看,mysqld
是一个独立的进程,它监听特定端口(默认为 3306)等待客户端连接。一旦有连接请求,mysqld
就会派生出一个线程(在多线程模型下)或进程(在多进程模型下)来处理该连接的交互。
mysqld 启动阶段核心函数调用
- main 函数入口
mysqld
的启动始于main
函数,这个函数位于mysqld.cc
文件中。在main
函数里,首先会进行一些基本的初始化操作,如设置信号处理函数,这些函数用于处理诸如 SIGTERM(终止信号)、SIGINT(中断信号)等系统信号,确保mysqld
在接收到这些信号时能进行安全的关闭或其他处理。- 示例代码片段(简化示意):
int main(int argc, char **argv) {
// 设置 SIGTERM 信号处理函数
signal(SIGTERM, mysqld_signal_handler);
// 更多初始化操作
//...
return 0;
}
- 选项解析
- 接下来,
main
函数会调用handle_options
函数来解析命令行选项和配置文件选项。这些选项涵盖了数据库的运行模式(如安全模式、只读模式等)、数据目录位置、日志文件路径等重要设置。 handle_options
函数会遍历命令行参数和配置文件中的设置项,将其解析为内部可识别的变量。例如,通过--datadir
选项指定的数据目录路径会被解析并存储在相应的变量中,供后续数据库初始化使用。
- 接下来,
- 初始化关键组件
- 内存分配初始化:
mysqld
调用mem_init
函数来初始化内存分配模块。这个模块对于数据库高效运行至关重要,它负责管理各种数据结构(如缓冲池、查询缓存等)的内存分配和释放。 - 日志系统初始化:
log_init
函数用于初始化日志系统。数据库日志分为二进制日志(用于数据备份和主从复制)、重做日志(用于崩溃恢复)等。log_init
函数会配置日志文件的路径、大小限制等参数。 - 存储引擎初始化:
ha_init
函数负责初始化存储引擎。MariaDB 支持多种存储引擎,如 InnoDB、MyISAM 等。ha_init
函数会遍历已编译进系统的存储引擎列表,调用每个存储引擎的初始化函数。例如,对于 InnoDB 存储引擎,会初始化其缓冲池、锁管理器等组件。
- 内存分配初始化:
客户端连接处理核心函数调用
- 监听端口与接受连接
mysqld
使用socket
系统调用创建一个监听套接字,并绑定到指定端口(如 3306)。这一过程在mysqld.cc
的相关网络初始化代码中实现。- 示例代码(简化网络初始化部分):
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(3306);
bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listen_sock, BACKLOG);
- 当有客户端连接请求时,
mysqld
调用accept
函数接受连接。accept
函数返回一个新的套接字描述符,用于与客户端进行通信。
- 连接线程创建与处理
- 在多线程模型下,
mysqld
会为每个新连接创建一个线程来处理。pthread_create
函数用于创建新线程,线程的执行函数通常是handle_connection
。 handle_connection
函数负责与客户端进行交互,包括认证客户端身份、接收和解析客户端发送的 SQL 语句等。- 示例代码(简化线程创建部分):
- 在多线程模型下,
pthread_t new_thread;
pthread_create(&new_thread, NULL, handle_connection, (void *)new_sockfd);
- 客户端认证
- 在
handle_connection
函数中,首先进行客户端认证。认证过程涉及到验证客户端提供的用户名、密码等信息。check_connection
函数在这一过程中起到关键作用,它会查询数据库中的用户表(如mysql.user
表),比对客户端提供的认证信息与存储在表中的信息是否匹配。 - 如果认证成功,客户端可以继续与数据库进行交互;否则,
mysqld
会关闭连接并返回相应的错误信息。
- 在
SQL 语句处理核心函数调用
- SQL 语句接收与解析
- 客户端连接线程(由
handle_connection
管理)接收客户端发送的 SQL 语句。这些语句以字符串形式接收,存储在缓冲区中。 lex_and_parse
函数负责对 SQL 语句进行词法分析和语法分析。词法分析将输入的 SQL 字符串分解为一个个的词法单元(如关键字、标识符、操作符等),语法分析则根据这些词法单元构建语法树。例如,对于 SQL 语句SELECT * FROM users WHERE age > 18;
,词法分析会将其分解为SELECT
、*
、FROM
、users
、WHERE
、age
、>
、18
等词法单元,语法分析会构建一棵反映该语句逻辑结构的语法树。
- 客户端连接线程(由
- 查询优化
- 语法分析完成后,
mysqld
调用optimize_sql
函数进行查询优化。查询优化器会根据语法树和数据库的统计信息(如索引信息、表的行数等)生成最优的执行计划。 - 例如,对于上述
SELECT
语句,如果users
表的age
列上有索引,查询优化器可能会选择使用索引来加速查询,而不是全表扫描。优化器会考虑多种执行方案,并通过成本估算等算法选择成本最低的方案作为最终执行计划。
- 语法分析完成后,
- 存储引擎交互
- 一旦确定了执行计划,
mysqld
会根据存储引擎类型与相应的存储引擎进行交互。例如,如果表使用的是 InnoDB 存储引擎,mysqld
会调用 InnoDB 存储引擎的接口函数来执行查询。 - 对于
SELECT
语句,mysqld
可能会调用 InnoDB 的ha_innobase::rnd_next
函数来逐行读取数据。如果是INSERT
语句,会调用ha_innobase::write_row
函数将新行数据写入存储引擎。
- 一旦确定了执行计划,
事务处理核心函数调用
- 事务开始
- 当客户端发送
START TRANSACTION
语句时,mysqld
会调用start_transaction
函数。这个函数会初始化事务相关的数据结构,如事务 ID、回滚段等。 - 在 InnoDB 存储引擎中,事务 ID 是一个递增的数字,用于唯一标识每个事务。回滚段则用于存储事务执行过程中的回滚信息,以便在事务回滚时恢复数据到事务开始前的状态。
- 当客户端发送
- 事务操作
- 在事务执行过程中,对数据的修改操作(如
INSERT
、UPDATE
、DELETE
)会被记录到日志中。对于 InnoDB 存储引擎,这些操作会先记录到重做日志(redo log)和回滚日志(undo log)中。 - 例如,当执行
UPDATE users SET age = age + 1 WHERE name = 'John';
语句时,InnoDB 会先将该操作记录到重做日志和回滚日志中。重做日志用于崩溃恢复,确保在数据库崩溃后能重新应用这些修改;回滚日志用于事务回滚,在需要时撤销该修改。
- 在事务执行过程中,对数据的修改操作(如
- 事务提交与回滚
- 当客户端发送
COMMIT
语句时,mysqld
调用commit_transaction
函数。这个函数会将事务的所有修改持久化到存储引擎中,并释放事务占用的资源。在 InnoDB 中,会将重做日志中的修改刷新到磁盘,同时清理回滚段中与该事务相关的信息。 - 当客户端发送
ROLLBACK
语句时,mysqld
调用rollback_transaction
函数。该函数会根据回滚日志中的信息撤销事务执行过程中的所有修改,恢复数据到事务开始前的状态。
- 当客户端发送
锁机制核心函数调用
- 锁请求
- 在多用户并发访问数据库时,为了保证数据的一致性,
mysqld
使用锁机制。当一个事务需要对数据进行修改时,会调用lock_request
函数请求相应的锁。 - 例如,对于
UPDATE users SET age = age + 1 WHERE name = 'John';
语句,在执行前会请求对users
表中满足name = 'John'
条件的行锁。锁请求函数会根据锁的类型(如共享锁、排他锁)和当前锁的持有情况进行处理。
- 在多用户并发访问数据库时,为了保证数据的一致性,
- 锁授予与等待
lock_grant
函数负责决定是否授予锁请求。如果请求的锁与当前已持有的锁不冲突(例如,请求共享锁,而当前没有排他锁持有),则授予锁;否则,请求线程会进入等待状态。- 等待锁的线程会被放入一个等待队列中。
lock_wait
函数管理等待线程的状态,当锁可用时,会从等待队列中唤醒相应的线程。
- 锁释放
- 当事务完成(提交或回滚)时,会调用
lock_release
函数释放持有的锁。例如,在事务提交后,commit_transaction
函数会调用lock_release
函数释放事务持有的所有锁,以便其他事务可以请求这些锁来访问数据。
- 当事务完成(提交或回滚)时,会调用
日志管理核心函数调用
- 二进制日志写入
- 二进制日志用于记录数据库的所有修改操作,主要用于数据备份和主从复制。
log_bin_write
函数负责将修改操作写入二进制日志。 - 例如,当执行
INSERT INTO products (name, price) VALUES ('Widget', 10.99);
语句时,log_bin_write
函数会将该INSERT
操作以二进制格式记录到二进制日志文件中。二进制日志文件的格式有特定的结构,包含事件头、事件数据等部分。
- 二进制日志用于记录数据库的所有修改操作,主要用于数据备份和主从复制。
- 重做日志写入
- 重做日志用于崩溃恢复,确保在数据库崩溃后能重新应用未完成事务的修改。
log_redo_write
函数负责将修改操作记录到重做日志中。 - 重做日志采用循环写的方式,当日志文件写满时,会覆盖旧的日志内容。在写入重做日志时,会先将修改操作记录到日志缓冲区中,然后根据一定的策略(如日志缓冲区满、事务提交等)将日志缓冲区的内容刷新到磁盘。
- 重做日志用于崩溃恢复,确保在数据库崩溃后能重新应用未完成事务的修改。
- 日志刷新与同步
log_flush
函数负责将日志缓冲区的内容刷新到磁盘。这一操作对于保证数据的持久性非常重要。在事务提交时,通常会调用log_flush
函数确保相关日志已持久化到磁盘。log_sync
函数用于确保日志数据已真正写入磁盘,而不仅仅是在操作系统的缓存中。通过调用fsync
等系统调用,log_sync
函数保证日志数据的可靠性,防止在系统崩溃时丢失数据。
错误处理核心函数调用
- 错误检测
- 在
mysqld
的各个处理阶段(如 SQL 解析、存储引擎操作等)都可能检测到错误。例如,在lex_and_parse
函数进行 SQL 解析时,如果发现语法错误,会设置相应的错误标志。 - 在存储引擎操作中,如果遇到磁盘 I/O 错误、数据校验错误等,也会触发错误检测机制。例如,InnoDB 在读取数据页时,如果发现数据页的校验和不正确,会标记一个错误。
- 在
- 错误报告
- 当错误被检测到后,
mysqld
调用report_error
函数来报告错误。这个函数会根据错误的严重程度(如警告、错误、致命错误等)采取不同的处理方式。 - 对于警告级别的错误,
report_error
可能只是记录一条日志信息;对于错误级别的错误,除了记录日志外,还可能向客户端返回错误信息;对于致命错误,report_error
可能会导致mysqld
进程终止。
- 当错误被检测到后,
- 错误恢复
- 在一些情况下,
mysqld
可以从错误中恢复。例如,如果是由于临时的磁盘空间不足导致写入日志失败,在空间恢复后,mysqld
可以通过重试操作来恢复正常运行。 - 对于更严重的错误,如存储引擎内部的数据结构损坏,可能需要手动干预(如进行数据库修复操作)才能使
mysqld
恢复正常。在这种情况下,mysqld
通常会记录详细的错误信息,帮助管理员定位和解决问题。
- 在一些情况下,
性能监控与统计核心函数调用
- 性能计数器初始化
mysqld
启动时,会调用init_perf_counters
函数来初始化性能计数器。这些计数器用于统计各种数据库操作的次数、时间等信息,如查询执行次数、锁等待时间、磁盘 I/O 次数等。- 例如,会初始化一个计数器用于统计
SELECT
语句的执行次数,每次执行SELECT
语句时,该计数器会递增。
- 性能数据收集
- 在数据库运行过程中,各个核心函数会在适当的位置更新性能计数器。例如,在
handle_connection
函数处理客户端连接时,会更新连接相关的计数器(如连接建立次数、连接断开次数)。 - 在执行 SQL 语句时,
execute_sql
函数会更新查询相关的计数器,如查询执行时间、查询返回行数等。
- 在数据库运行过程中,各个核心函数会在适当的位置更新性能计数器。例如,在
- 性能数据展示
show_perf_data
函数用于展示性能数据。可以通过SHOW STATUS
等 SQL 语句调用该函数,向用户展示当前数据库的性能统计信息。这些信息对于数据库管理员进行性能调优非常重要,例如通过查看锁等待时间的统计信息,可以判断是否存在锁争用问题,进而优化数据库设计或调整事务处理逻辑。