MariaDB 中 purge 命令清理 binlog 的底层实现
MariaDB 中 binlog 简介
binlog 的作用
MariaDB 中的二进制日志(binlog)是一种重要的日志机制,它记录了数据库中所有更改数据的操作,例如 INSERT、UPDATE 和 DELETE 语句等。binlog 的主要作用之一是用于数据备份和恢复。当数据库出现故障时,可以通过重放 binlog 中的记录来恢复到故障前的某个时间点,确保数据的一致性和完整性。
另一个关键作用是在主从复制架构中。主服务器将 binlog 发送给从服务器,从服务器通过重放这些日志来保持与主服务器的数据同步。这使得 MariaDB 能够构建高可用、可扩展的数据库集群。
binlog 的结构
binlog 由一系列的日志文件组成,每个文件都有一个唯一的文件名,通常命名格式为 hostname-bin.xxxxxx
,其中 xxxxxx
是一个递增的数字。每个 binlog 文件包含了多个日志事件(log event),这些事件记录了具体的数据库操作。
日志事件有不同的类型,例如 Format_description_log_event
用于描述 binlog 的格式信息,Query_log_event
用于记录 SQL 查询语句,Rows_log_event
用于记录行级别的数据更改等。每个日志事件都有特定的结构,包含事件头和事件体。事件头中包含了事件的类型、长度、时间戳等元信息,而事件体则包含了具体的操作数据。
purge 命令概述
purge 命令的功能
purge
命令在 MariaDB 中用于清理 binlog 文件。随着数据库的运行,binlog 文件会不断增长,如果不进行清理,会占用大量的磁盘空间。purge
命令允许管理员指定清理哪些 binlog 文件,确保磁盘空间得到合理利用。
例如,管理员可以使用 PURGE BINARY LOGS TO 'hostname-bin.000010';
命令来清理所有早于 hostname-bin.000010
的 binlog 文件。这样,系统会删除这些旧的 binlog 文件,释放相应的磁盘空间。
purge 命令的使用场景
- 磁盘空间管理:当数据库服务器磁盘空间紧张时,通过
purge
命令清理不再需要的 binlog 文件是一种有效的释放空间的方法。特别是在一些写入操作频繁的数据库中,binlog 文件增长迅速,定期清理 binlog 可以避免磁盘空间耗尽的问题。 - 主从复制维护:在主从复制环境中,主服务器上的 binlog 文件需要保留足够长的时间,以便从服务器能够获取并同步数据。但是,当从服务器已经成功同步了某些 binlog 文件中的数据后,主服务器上对应的 binlog 文件就可以安全地清理掉。通过
purge
命令,可以精确控制主服务器上 binlog 文件的保留策略,确保复制的稳定性和高效性。
purge 命令清理 binlog 的底层实现流程
1. 权限检查
当用户执行 purge
命令时,MariaDB 首先会进行权限检查。只有具有 RELOAD
和 SUPER
权限的用户才能执行 purge
操作。这是为了防止普通用户误操作导致 binlog 数据丢失,影响数据库的备份和复制功能。
在代码层面,权限检查主要涉及到 sql_parse.cc
文件中的相关逻辑。例如,在 mysql_parse()
函数中,当解析到 PURGE BINARY LOGS
语句时,会调用 check_access()
函数来检查当前用户是否具备相应权限。以下是简化的代码示例:
if (LEX_CMP(str, "PURGE BINARY LOGS", length) == 0) {
if (!check_access(SUPER_ACL, RELOAD_ACL)) {
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RELOAD and SUPER");
return;
}
// 权限通过,继续处理 purge 命令
}
2. 确定要清理的 binlog 文件范围
在权限检查通过后,MariaDB 需要确定要清理哪些 binlog 文件。这取决于 purge
命令的参数。如果使用 PURGE BINARY LOGS TO 'log_name';
格式,系统会清理所有早于指定 log_name
的 binlog 文件;如果使用 PURGE BINARY LOGS BEFORE 'date';
格式,系统会清理所有修改时间早于指定日期的 binlog 文件。
这部分逻辑主要在 log.cc
文件中实现。以 PURGE BINARY LOGS TO 'log_name';
为例,系统会遍历当前所有的 binlog 文件列表,对比文件名来确定要清理的文件范围。以下是相关代码的简化示意:
List<IO_CACHE> binlog_files;
// 获取所有 binlog 文件列表
get_binlog_files(binlog_files);
const char *target_log_name = "hostname-bin.000010"; // 假设的目标文件名
IO_CACHE *file_to_start = nullptr;
for (IO_CACHE *file : binlog_files) {
if (strcmp(file->name, target_log_name) == 0) {
file_to_start = file;
break;
}
}
if (file_to_start) {
IO_CACHE *current_file = binlog_files.first;
while (current_file && current_file != file_to_start) {
// 这里可以添加删除文件的逻辑
my_delete(current_file->name, MYF(0));
current_file = current_file->next;
}
}
3. 检查主从复制状态
在真正删除 binlog 文件之前,MariaDB 会检查主从复制状态。如果当前服务器是主服务器,并且有从服务器正在同步数据,那么系统需要确保不会删除从服务器还未同步的 binlog 文件。
这一检查过程涉及到与复制相关的元数据。MariaDB 会查询 mysql.slave_master_info
表(在从服务器上)或内部维护的复制状态信息(在主服务器上),来确定哪些 binlog 文件已经被从服务器成功接收和应用。
在 rpl_master.cc
文件中,有相关逻辑用于在主服务器上检查从服务器的复制进度。例如,Master_info::is_log_file_needed()
函数可以判断某个 binlog 文件是否还被从服务器需要。简化代码如下:
bool Master_info::is_log_file_needed(const char *log_name) {
// 遍历从服务器列表,检查每个从服务器的复制进度
for (Slave_status *slave : slave_list) {
if (strcmp(slave->get_master_log_name(), log_name) >= 0) {
return true;
}
}
return false;
}
4. 删除 binlog 文件
在经过前面的权限检查、文件范围确定和主从复制状态检查后,MariaDB 会开始删除确定要清理的 binlog 文件。这一过程相对较为直接,通过调用操作系统的文件删除函数(如 unlink
或 my_delete
)来删除对应的 binlog 文件。
例如,在 log.cc
文件中,当确定要删除某个 binlog 文件时,会调用 my_delete
函数:
my_delete(file->name, MYF(0));
这里的 file->name
就是要删除的 binlog 文件的路径和文件名。在删除文件后,MariaDB 还会更新内部维护的 binlog 文件列表,确保系统对 binlog 文件的状态有准确的记录。
5. 更新相关元数据
在 binlog 文件被删除后,MariaDB 需要更新相关的元数据。例如,更新 mysql.slave_master_info
表(在从服务器上)或内部维护的 binlog 索引信息(在主服务器上),以反映 binlog 文件的变化。
在主服务器上,log.cc
文件中的 update_binlog_index()
函数会负责更新 binlog 索引。简化代码如下:
void update_binlog_index() {
// 重新构建 binlog 索引信息,例如将已删除文件从索引中移除
List<IO_CACHE> binlog_files;
get_binlog_files(binlog_files);
// 重新生成索引文件内容,省略具体实现
}
在从服务器上,当检测到主服务器上的 binlog 文件被删除后,会通过复制协议获取新的 binlog 索引信息,并更新本地的 mysql.slave_master_info
表,确保从服务器能够准确地从主服务器获取后续的 binlog 数据。
底层实现中的关键数据结构与函数
关键数据结构
- IO_CACHE:这个结构用于管理 binlog 文件的输入输出缓存。在 MariaDB 中,每个 binlog 文件都由一个
IO_CACHE
实例来表示。它包含了文件的路径名、文件描述符、缓存区等信息。例如,在log.cc
文件中,通过IO_CACHE
结构来操作 binlog 文件的读写:
struct IO_CACHE {
char *name;
File file;
char *buffer;
// 其他缓存管理相关的成员变量
};
- Master_info:在主从复制环境中,
Master_info
结构用于存储主服务器的相关信息,包括主服务器的地址、端口、复制用户信息以及从服务器的复制进度等。如前文提到的is_log_file_needed()
函数就是Master_info
结构的成员函数,用于判断某个 binlog 文件是否还被从服务器需要。
class Master_info {
public:
char *master_host;
uint16 master_port;
char *user;
char *password;
// 从服务器复制进度相关的成员变量
bool is_log_file_needed(const char *log_name);
};
- Slave_status:从服务器使用这个结构来存储自身的复制状态信息,如当前正在同步的主服务器的 binlog 文件和位置等。在主服务器检查从服务器复制进度时,会遍历
Slave_status
实例列表来获取相关信息。
class Slave_status {
public:
char *master_log_name;
long master_log_pos;
// 其他复制状态相关的成员变量
};
关键函数
- check_access():这个函数位于
sql_parse.cc
文件中,用于检查用户是否具备执行特定操作(如purge
命令)所需的权限。它接受权限类型作为参数,并根据当前用户的权限设置返回相应的结果。
bool check_access(uint access_type, uint additional_type) {
// 检查用户权限逻辑,简化示意
if (current_user_has_permission(access_type) && current_user_has_permission(additional_type)) {
return true;
}
return false;
}
- get_binlog_files():在
log.cc
文件中,该函数用于获取当前所有的 binlog 文件列表,并将这些文件信息存储在一个List<IO_CACHE>
容器中。这个列表在确定要清理的 binlog 文件范围以及更新 binlog 索引等操作中都起到关键作用。
void get_binlog_files(List<IO_CACHE> &binlog_files) {
// 遍历 binlog 文件目录,将每个文件信息填充到 binlog_files 中
DIR *dir = opendir(binlog_dir);
struct dirent *entry;
while ((entry = readdir(dir))!= nullptr) {
if (is_binlog_file(entry->d_name)) {
IO_CACHE *cache = new IO_CACHE();
cache->name = strdup(entry->d_name);
// 打开文件并设置文件描述符等操作,省略具体实现
binlog_files.push_back(cache);
}
}
closedir(dir);
}
- update_binlog_index():同样在
log.cc
文件中,该函数用于在 binlog 文件发生变化(如删除)后,更新系统内部维护的 binlog 索引信息。它会重新扫描当前存在的 binlog 文件,并生成新的索引内容,确保系统能够准确地定位和管理 binlog 文件。
void update_binlog_index() {
List<IO_CACHE> binlog_files;
get_binlog_files(binlog_files);
// 根据 binlog_files 重新生成索引文件内容,省略具体实现
}
异常处理与注意事项
异常处理
- 权限不足异常:如果用户在执行
purge
命令时没有足够的权限,MariaDB 会抛出ER_SPECIFIC_ACCESS_DENIED_ERROR
错误,提示用户需要RELOAD
和SUPER
权限。在sql_parse.cc
文件中的权限检查部分,如前文所述,通过my_error()
函数来抛出这个错误:
if (!check_access(SUPER_ACL, RELOAD_ACL)) {
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RELOAD and SUPER");
return;
}
- 文件删除失败异常:在删除 binlog 文件时,如果由于文件系统权限问题、文件正在被其他进程使用等原因导致文件删除失败,MariaDB 会记录错误日志。在
log.cc
文件中,调用my_delete
函数删除文件时,如果返回非零值(表示删除失败),可以进行相应的错误处理:
if (my_delete(file->name, MYF(0))!= 0) {
my_error(ER_COULD_NOT_DELETE_FILE, MYF(0), file->name);
// 可以选择继续尝试删除或进行其他处理
}
- 主从复制冲突异常:当主服务器在清理 binlog 文件时,如果检测到某个要删除的 binlog 文件仍被从服务器需要,MariaDB 会停止删除操作,并记录相应的警告信息。在
rpl_master.cc
文件中,Master_info::is_log_file_needed()
函数用于检测这种情况,如果某个 binlog 文件被从服务器需要,会阻止该文件的删除。
bool Master_info::is_log_file_needed(const char *log_name) {
for (Slave_status *slave : slave_list) {
if (strcmp(slave->get_master_log_name(), log_name) >= 0) {
// 记录警告信息,提示该文件不能删除
my_warning(ER_BINLOG_CANNOT_DELETE, MYF(0), log_name);
return true;
}
}
return false;
}
注意事项
-
谨慎使用 purge 命令:由于
purge
命令会永久性地删除 binlog 文件,在执行之前一定要确保这些文件不再被用于备份、恢复或主从复制。特别是在主从复制环境中,错误地删除从服务器还未同步的 binlog 文件可能导致数据不一致或复制中断。 -
定期清理与监控:为了避免 binlog 文件占用过多磁盘空间,建议定期执行
purge
命令进行清理。同时,要监控 binlog 文件的增长速度和磁盘空间使用情况,根据实际情况调整清理策略。可以通过设置合理的purge
周期,如每周或每月清理一次,确保磁盘空间的有效利用。 -
备份与恢复考虑:在执行
purge
命令之前,要确认数据库的备份策略是否完整。如果依赖 binlog 进行基于时间点的恢复(Point-in-Time Recovery, PITR),确保在清理 binlog 时不会影响到备份的有效性。可以结合全量备份和 binlog 备份来制定全面的数据恢复方案,在清理 binlog 时不会丢失重要的恢复数据。 -
主从复制拓扑变化:当主从复制拓扑发生变化,如添加或移除从服务器时,要重新评估 binlog 文件的保留策略。新添加的从服务器可能需要从较早的 binlog 文件开始同步数据,因此在这种情况下,可能需要暂时保留更多的 binlog 文件,直到新从服务器完成初始同步。
-
系统性能影响:虽然
purge
命令本身不会对数据库的正常运行产生太大的性能影响,但在删除大量 binlog 文件时,可能会对文件系统的 I/O 性能产生一定压力。因此,建议在系统负载较低的时间段执行purge
操作,以减少对业务的影响。同时,可以通过优化文件系统配置,如使用高性能的存储设备或调整文件系统参数,来降低这种性能影响。