MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

mysqld核心函数调用流程解析

2021-09-222.3k 阅读

MariaDB 中 mysqld 进程概述

在 MariaDB 数据库系统里,mysqld 是核心进程,负责管理数据库文件、处理客户端连接、执行 SQL 语句等关键任务。mysqld 启动后,会初始化一系列关键组件,如内存分配模块、日志系统、存储引擎等,为数据库的正常运行奠定基础。

从操作系统层面看,mysqld 是一个独立的进程,它监听特定端口(默认为 3306)等待客户端连接。一旦有连接请求,mysqld 就会派生出一个线程(在多线程模型下)或进程(在多进程模型下)来处理该连接的交互。

mysqld 启动阶段核心函数调用

  1. main 函数入口
    • mysqld 的启动始于 main 函数,这个函数位于 mysqld.cc 文件中。在 main 函数里,首先会进行一些基本的初始化操作,如设置信号处理函数,这些函数用于处理诸如 SIGTERM(终止信号)、SIGINT(中断信号)等系统信号,确保 mysqld 在接收到这些信号时能进行安全的关闭或其他处理。
    • 示例代码片段(简化示意):
int main(int argc, char **argv) {
    // 设置 SIGTERM 信号处理函数
    signal(SIGTERM, mysqld_signal_handler);
    // 更多初始化操作
    //...
    return 0;
}
  1. 选项解析
    • 接下来,main 函数会调用 handle_options 函数来解析命令行选项和配置文件选项。这些选项涵盖了数据库的运行模式(如安全模式、只读模式等)、数据目录位置、日志文件路径等重要设置。
    • handle_options 函数会遍历命令行参数和配置文件中的设置项,将其解析为内部可识别的变量。例如,通过 --datadir 选项指定的数据目录路径会被解析并存储在相应的变量中,供后续数据库初始化使用。
  2. 初始化关键组件
    • 内存分配初始化mysqld 调用 mem_init 函数来初始化内存分配模块。这个模块对于数据库高效运行至关重要,它负责管理各种数据结构(如缓冲池、查询缓存等)的内存分配和释放。
    • 日志系统初始化log_init 函数用于初始化日志系统。数据库日志分为二进制日志(用于数据备份和主从复制)、重做日志(用于崩溃恢复)等。log_init 函数会配置日志文件的路径、大小限制等参数。
    • 存储引擎初始化ha_init 函数负责初始化存储引擎。MariaDB 支持多种存储引擎,如 InnoDB、MyISAM 等。ha_init 函数会遍历已编译进系统的存储引擎列表,调用每个存储引擎的初始化函数。例如,对于 InnoDB 存储引擎,会初始化其缓冲池、锁管理器等组件。

客户端连接处理核心函数调用

  1. 监听端口与接受连接
    • 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 函数返回一个新的套接字描述符,用于与客户端进行通信。
  1. 连接线程创建与处理
    • 在多线程模型下,mysqld 会为每个新连接创建一个线程来处理。pthread_create 函数用于创建新线程,线程的执行函数通常是 handle_connection
    • handle_connection 函数负责与客户端进行交互,包括认证客户端身份、接收和解析客户端发送的 SQL 语句等。
    • 示例代码(简化线程创建部分):
pthread_t new_thread;
pthread_create(&new_thread, NULL, handle_connection, (void *)new_sockfd);
  1. 客户端认证
    • handle_connection 函数中,首先进行客户端认证。认证过程涉及到验证客户端提供的用户名、密码等信息。check_connection 函数在这一过程中起到关键作用,它会查询数据库中的用户表(如 mysql.user 表),比对客户端提供的认证信息与存储在表中的信息是否匹配。
    • 如果认证成功,客户端可以继续与数据库进行交互;否则,mysqld 会关闭连接并返回相应的错误信息。

SQL 语句处理核心函数调用

  1. SQL 语句接收与解析
    • 客户端连接线程(由 handle_connection 管理)接收客户端发送的 SQL 语句。这些语句以字符串形式接收,存储在缓冲区中。
    • lex_and_parse 函数负责对 SQL 语句进行词法分析和语法分析。词法分析将输入的 SQL 字符串分解为一个个的词法单元(如关键字、标识符、操作符等),语法分析则根据这些词法单元构建语法树。例如,对于 SQL 语句 SELECT * FROM users WHERE age > 18;,词法分析会将其分解为 SELECT*FROMusersWHEREage>18 等词法单元,语法分析会构建一棵反映该语句逻辑结构的语法树。
  2. 查询优化
    • 语法分析完成后,mysqld 调用 optimize_sql 函数进行查询优化。查询优化器会根据语法树和数据库的统计信息(如索引信息、表的行数等)生成最优的执行计划。
    • 例如,对于上述 SELECT 语句,如果 users 表的 age 列上有索引,查询优化器可能会选择使用索引来加速查询,而不是全表扫描。优化器会考虑多种执行方案,并通过成本估算等算法选择成本最低的方案作为最终执行计划。
  3. 存储引擎交互
    • 一旦确定了执行计划,mysqld 会根据存储引擎类型与相应的存储引擎进行交互。例如,如果表使用的是 InnoDB 存储引擎,mysqld 会调用 InnoDB 存储引擎的接口函数来执行查询。
    • 对于 SELECT 语句,mysqld 可能会调用 InnoDB 的 ha_innobase::rnd_next 函数来逐行读取数据。如果是 INSERT 语句,会调用 ha_innobase::write_row 函数将新行数据写入存储引擎。

事务处理核心函数调用

  1. 事务开始
    • 当客户端发送 START TRANSACTION 语句时,mysqld 会调用 start_transaction 函数。这个函数会初始化事务相关的数据结构,如事务 ID、回滚段等。
    • 在 InnoDB 存储引擎中,事务 ID 是一个递增的数字,用于唯一标识每个事务。回滚段则用于存储事务执行过程中的回滚信息,以便在事务回滚时恢复数据到事务开始前的状态。
  2. 事务操作
    • 在事务执行过程中,对数据的修改操作(如 INSERTUPDATEDELETE)会被记录到日志中。对于 InnoDB 存储引擎,这些操作会先记录到重做日志(redo log)和回滚日志(undo log)中。
    • 例如,当执行 UPDATE users SET age = age + 1 WHERE name = 'John'; 语句时,InnoDB 会先将该操作记录到重做日志和回滚日志中。重做日志用于崩溃恢复,确保在数据库崩溃后能重新应用这些修改;回滚日志用于事务回滚,在需要时撤销该修改。
  3. 事务提交与回滚
    • 当客户端发送 COMMIT 语句时,mysqld 调用 commit_transaction 函数。这个函数会将事务的所有修改持久化到存储引擎中,并释放事务占用的资源。在 InnoDB 中,会将重做日志中的修改刷新到磁盘,同时清理回滚段中与该事务相关的信息。
    • 当客户端发送 ROLLBACK 语句时,mysqld 调用 rollback_transaction 函数。该函数会根据回滚日志中的信息撤销事务执行过程中的所有修改,恢复数据到事务开始前的状态。

锁机制核心函数调用

  1. 锁请求
    • 在多用户并发访问数据库时,为了保证数据的一致性,mysqld 使用锁机制。当一个事务需要对数据进行修改时,会调用 lock_request 函数请求相应的锁。
    • 例如,对于 UPDATE users SET age = age + 1 WHERE name = 'John'; 语句,在执行前会请求对 users 表中满足 name = 'John' 条件的行锁。锁请求函数会根据锁的类型(如共享锁、排他锁)和当前锁的持有情况进行处理。
  2. 锁授予与等待
    • lock_grant 函数负责决定是否授予锁请求。如果请求的锁与当前已持有的锁不冲突(例如,请求共享锁,而当前没有排他锁持有),则授予锁;否则,请求线程会进入等待状态。
    • 等待锁的线程会被放入一个等待队列中。lock_wait 函数管理等待线程的状态,当锁可用时,会从等待队列中唤醒相应的线程。
  3. 锁释放
    • 当事务完成(提交或回滚)时,会调用 lock_release 函数释放持有的锁。例如,在事务提交后,commit_transaction 函数会调用 lock_release 函数释放事务持有的所有锁,以便其他事务可以请求这些锁来访问数据。

日志管理核心函数调用

  1. 二进制日志写入
    • 二进制日志用于记录数据库的所有修改操作,主要用于数据备份和主从复制。log_bin_write 函数负责将修改操作写入二进制日志。
    • 例如,当执行 INSERT INTO products (name, price) VALUES ('Widget', 10.99); 语句时,log_bin_write 函数会将该 INSERT 操作以二进制格式记录到二进制日志文件中。二进制日志文件的格式有特定的结构,包含事件头、事件数据等部分。
  2. 重做日志写入
    • 重做日志用于崩溃恢复,确保在数据库崩溃后能重新应用未完成事务的修改。log_redo_write 函数负责将修改操作记录到重做日志中。
    • 重做日志采用循环写的方式,当日志文件写满时,会覆盖旧的日志内容。在写入重做日志时,会先将修改操作记录到日志缓冲区中,然后根据一定的策略(如日志缓冲区满、事务提交等)将日志缓冲区的内容刷新到磁盘。
  3. 日志刷新与同步
    • log_flush 函数负责将日志缓冲区的内容刷新到磁盘。这一操作对于保证数据的持久性非常重要。在事务提交时,通常会调用 log_flush 函数确保相关日志已持久化到磁盘。
    • log_sync 函数用于确保日志数据已真正写入磁盘,而不仅仅是在操作系统的缓存中。通过调用 fsync 等系统调用,log_sync 函数保证日志数据的可靠性,防止在系统崩溃时丢失数据。

错误处理核心函数调用

  1. 错误检测
    • mysqld 的各个处理阶段(如 SQL 解析、存储引擎操作等)都可能检测到错误。例如,在 lex_and_parse 函数进行 SQL 解析时,如果发现语法错误,会设置相应的错误标志。
    • 在存储引擎操作中,如果遇到磁盘 I/O 错误、数据校验错误等,也会触发错误检测机制。例如,InnoDB 在读取数据页时,如果发现数据页的校验和不正确,会标记一个错误。
  2. 错误报告
    • 当错误被检测到后,mysqld 调用 report_error 函数来报告错误。这个函数会根据错误的严重程度(如警告、错误、致命错误等)采取不同的处理方式。
    • 对于警告级别的错误,report_error 可能只是记录一条日志信息;对于错误级别的错误,除了记录日志外,还可能向客户端返回错误信息;对于致命错误,report_error 可能会导致 mysqld 进程终止。
  3. 错误恢复
    • 在一些情况下,mysqld 可以从错误中恢复。例如,如果是由于临时的磁盘空间不足导致写入日志失败,在空间恢复后,mysqld 可以通过重试操作来恢复正常运行。
    • 对于更严重的错误,如存储引擎内部的数据结构损坏,可能需要手动干预(如进行数据库修复操作)才能使 mysqld 恢复正常。在这种情况下,mysqld 通常会记录详细的错误信息,帮助管理员定位和解决问题。

性能监控与统计核心函数调用

  1. 性能计数器初始化
    • mysqld 启动时,会调用 init_perf_counters 函数来初始化性能计数器。这些计数器用于统计各种数据库操作的次数、时间等信息,如查询执行次数、锁等待时间、磁盘 I/O 次数等。
    • 例如,会初始化一个计数器用于统计 SELECT 语句的执行次数,每次执行 SELECT 语句时,该计数器会递增。
  2. 性能数据收集
    • 在数据库运行过程中,各个核心函数会在适当的位置更新性能计数器。例如,在 handle_connection 函数处理客户端连接时,会更新连接相关的计数器(如连接建立次数、连接断开次数)。
    • 在执行 SQL 语句时,execute_sql 函数会更新查询相关的计数器,如查询执行时间、查询返回行数等。
  3. 性能数据展示
    • show_perf_data 函数用于展示性能数据。可以通过 SHOW STATUS 等 SQL 语句调用该函数,向用户展示当前数据库的性能统计信息。这些信息对于数据库管理员进行性能调优非常重要,例如通过查看锁等待时间的统计信息,可以判断是否存在锁争用问题,进而优化数据库设计或调整事务处理逻辑。