热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MySQL源代码解析:二进制日志崩溃恢复机制深入探讨

本文详细解析了MySQL5.7.20版本中二进制日志(binlog)崩溃恢复机制的工作流程。假设使用InnoDB存储引擎,并且启用了`sync_binlog=1`配置,文章深入探讨了在系统崩溃后如何通过binlog进行数据恢复,确保数据的一致性和完整性。

前言

本文主要介绍binlog crash recovery 的过程

假设用户使用 InnoDB 引擎,sync_binlog=1

使用 MySQL 5.7.20 版本进行分析

crash recovery 过程中,binlog 需要保证:

  1. 所有已提交事务的binlog已存在
  2. 所有未提交事务的binlog不存在

两阶段提交

MySQL 使用两阶段提交解决 binlog 和 InnoDB redo log 的一致性的问题

也就是将普通事务当做内部XA事务处理,为每个事务分配一个XID,binlog作为事务的协调者

  • 阶段1:InnoDB redo log 写盘,InnoDB 事务进入 prepare 状态
  • 阶段2:binlog 写盘,InooDB 事务进入 commit 状态

每个事务binlog的末尾,会记录一个 XID event,标志着事务是否提交成功,也就是说,recovery 过程中,binlog 最后一个 XID event 之后的内容都应该被 purge。

InnoDB 日志可能也需要回滚或者提交,这里就不再展开。

binlog 文件的 crash recovery

mysqld_maininit_server_componentsMYSQL_BIN_LOG::openMYSQL_BIN_LOG::open_binlog

binlog recover 的主要过程在 MYSQL_BIN_LOG::open_binlog 中

int MYSQL_BIN_LOG::open_binlog(const char *opt_name)
{/* 确保 index 文件初始化成功 */if (!my_b_inited(&index_file)) {/* There was a failure to open the index file, can&#39;t open the binlog */cleanup();return 1;}/* 找到 index 中第一个 binlog */if ((error&#61; find_log_pos(&log_info, NullS, true/*need_lock_index&#61;true*/))){/* 找到 index 中最后一个 binlog */do{strmake(log_name, log_info.log_file_name, sizeof(log_name)-1); } while (!(error&#61; find_next_log(&log_info, true/*need_lock_index&#61;true*/)));/*打开最后一个binlog&#xff0c;会校验文件头的 magic number "\xfe\x62\x69\x6e"如果 magic number 校验失败&#xff0c;会直接报错退出&#xff0c;无法完成recovery如果确定最后一个binlog没有内容&#xff0c;可以删除binlog 文件再重试*/if ((file&#61; open_binlog_file(&log, log_name, &errmsg)) <0)/*如果 binlog 没有正常关闭&#xff0c;mysql server 可能crash过&#xff0c;我们需要调用 MYSQL_BIN_LOG::recover&#xff1a;a) 找到最后一个 XIDb) 完成最后一个事务的两阶段提交&#xff08;InnoDB commit&#xff09;c) 找到最后一个合法位点因此&#xff0c;我们需要遍历 binlog 文件&#xff0c;找到最后一个合法event集合&#xff0c;并 purge 无效binlog*/if ((ev&#61; Log_event::read_log_event(&log, 0, &fdle,opt_master_verify_checksum)) &&ev->get_type_code() &#61;&#61; binary_log::FORMAT_DESCRIPTION_EVENT &&(ev->common_header->flags & LOG_EVENT_BINLOG_IN_USE_F ||DBUG_EVALUATE_IF("eval_force_bin_log_recovery", true, false))){sql_print_information("Recovering after a crash using %s", opt_name); /* 初始化合法位点 */ valid_pos&#61; my_b_tell(&log);/* 执行recover 过程 &#xff0c;并计算出合法位点 */error&#61; recover(&log, (Format_description_log_event *)ev, &valid_pos);}elseerror&#61;0;if (valid_pos > 0){if (valid_pos }

recover 函数的逻辑很简单&#xff1a;遍历最后一个binlog的所有 event&#xff0c;每次事务结尾&#xff0c;或者非事务event结尾更新 valid_pos(gtid event不更新)。并在一个 hash 中记录所有xid&#xff0c;用于引擎层 recover

int MYSQL_BIN_LOG::recover(IO_CACHE *log, Format_description_log_event *fdle,my_off_t *valid_pos)
{/* 初始化 XID hash&#xff0c;用于记录 binlog 中的 xid */if (! fdle->is_valid() || my_hash_init(&xids, &my_charset_bin, TC_LOG_PAGE_SIZE/3, 0,sizeof(my_xid), 0, 0, MYF(0),key_memory_binlog_recover_exec))goto err1;/* 依次读取 binlog event */while ((ev&#61; Log_event::read_log_event(log, 0, fdle, TRUE))&& ev->is_valid()){if (ev->get_type_code() &#61;&#61; binary_log::QUERY_EVENT &&!strcmp(((Query_log_event*)ev)->query, "BEGIN"))/* begin 代表事务开始 */in_transaction&#61; TRUE;if (ev->get_type_code() &#61;&#61; binary_log::QUERY_EVENT &&!strcmp(((Query_log_event*)ev)->query, "COMMIT")){DBUG_ASSERT(in_transaction &#61;&#61; TRUE);/* commit 代表事务结束 */in_transaction&#61; FALSE;}else if (ev->get_type_code() &#61;&#61; binary_log::XID_EVENT){DBUG_ASSERT(in_transaction &#61;&#61; TRUE);/* xid event 代表事务结束 */in_transaction&#61; FALSE;Xid_log_event *xev&#61;(Xid_log_event *)ev;uchar *x&#61; (uchar *) memdup_root(&mem_root, (uchar*) &xev->xid,sizeof(xev->xid));/* 记录 xid */if (!x || my_hash_insert(&xids, x))goto err2;}/*如果不在事务中&#xff0c;且不是gtid event&#xff0c;则更新 valid_pos显然&#xff0c;如果在事务中&#xff0c;最后一段 event 不是一个完整事务&#xff0c;pos并不合法*/if (!log->error && !in_transaction &&!is_gtid_event(ev))*valid_pos&#61; my_b_tell(log);}/*存储引擎recover所有已经记录 XID 的事务必须在存储引擎中提交未记录 XID 的事务必须回滚*/if (total_ha_2pc > 1 && ha_recover(&xids))goto err2;

binlog index 的 crash recovery

为了保证 binlog index 的 crash safe&#xff0c;MySQL 引入了一个临时文件 crash_safe_index_file

新的 binlog_file_name 写入 binlog_index_file 流程如下&#xff1a;

  • 创建临时文件 crash_safe_index_file
  • 拷贝 binlog_index_file 中的内容到 crash_safe_index_file
  • 新的 binlog_file_name 写入 crash_safe_index_file
  • 删除 binlog_index_file
  • 重命名 crash_safe_index_file 到 binlog_index_file

这个流程保证了在任何时候crash&#xff0c;binlog_index_file 和 crash_safe_index_file 至少有一个可用

这样再recover 时只要判断这两个文件是否可用&#xff0c;如果 binlog_index_file 可用则无需特殊处理&#xff0c;如果binlog_index_file 不可用则重命名 crash_safe_index_file 到 binlog_index_file

binlog index 的 recover 过程主要在 bool MYSQL_BIN_LOG::open_index_file 中

显然&#xff0c;open_indix_file 在 open_binlog 之前

mysqld_maininit_server_componentsMYSQL_BIN_LOG::open_index_file


bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,const char *log_name, bool need_lock_index)
{/* 拼接 index_file_name */fn_format(index_file_name, index_file_name_arg, mysql_data_home,".index", opt); /* 拼接 crash_safe_index_file_name */if (set_crash_safe_index_file_name(index_file_name_arg))/*recover 主要体现在这里检查 index_file_name 和 crash_safe_index_file_name 是否存在如果 index_file_name 不存在 crash_safe_index_file_name 存在&#xff0c;那么将 crash_safe_index_file_name 重命名为 index_file_name*/if (my_access(index_file_name, F_OK) &&!my_access(crash_safe_index_file_name, F_OK) &&my_rename(crash_safe_index_file_name, index_file_name, MYF(MY_WME))){sql_print_error("MYSQL_BIN_LOG::open_index_file failed to ""move crash_safe_index_file to index file.");error&#61; true;goto end;}}

新的 binlog_file_name 写入 binlog_index_file 的过程在 MYSQL_BIN_LOG::add_log_to_index

int MYSQL_BIN_LOG::add_log_to_index(uchar* log_name,size_t log_name_len, bool need_lock_index)
{/* 创建 crash_safe_index_file */if (open_crash_safe_index_file())/* 拷贝 index_file 内容到 crash_safe_index_file */if (copy_file(&index_file, &crash_safe_index_file, 0))/* 写入 binlog_file_name */if (my_b_write(&crash_safe_index_file, log_name, log_name_len) ||my_b_write(&crash_safe_index_file, (uchar*) "\n", 1) ||flush_io_cache(&crash_safe_index_file) ||mysql_file_sync(crash_safe_index_file.file, MYF(MY_WME)))/*函数内部先 delete binlog_index_file 再 rename crash_safe_index_file如果 delete 到 rename 之间发生 crash&#xff0c; crash_safe_index_file 会在 recover过程中 rename 成 binlog_index_file*/if (move_crash_safe_index_file_to_index_file(need_lock_index))}

总结

MySQL 解决了binlog crash safe 的问题&#xff0c;但是 relay log 依然不保证 crash safe。

relay log 结构和 binlog 一致&#xff0c;可以借鉴 binlog crash safe 的方式&#xff0c;计算出 valid_pos&#xff0c;将 valid_pos之后的 event 全部purge。



推荐阅读
  • 技术日志:Ansible的安装及模块管理详解 ... [详细]
  • JBPM 6.5 环境配置深入解析(下篇)
    本文深入探讨了JBPM 6.5 的环境配置细节,从零开始详细介绍了下载、解压后的文件结构,并结合实际操作步骤,为初学者提供了全面的配置指南。通过具体的示例和详细的解释,帮助读者快速掌握 JBPM 6.5 的安装与配置过程。 ... [详细]
  • 在过去,我曾使用过自建MySQL服务器中的MyISAM和InnoDB存储引擎(也曾尝试过Memory引擎)。今年初,我开始转向阿里云的关系型数据库服务,并深入研究了其高效的压缩存储引擎TokuDB。TokuDB在数据压缩和处理大规模数据集方面表现出色,显著提升了存储效率和查询性能。通过实际应用,我发现TokuDB不仅能够有效减少存储成本,还能显著提高数据处理速度,特别适用于高并发和大数据量的场景。 ... [详细]
  • 在搭建Hadoop集群以处理大规模数据存储和频繁读取需求的过程中,经常会遇到各种配置难题。本文总结了作者在实际部署中遇到的典型问题,并提供了详细的解决方案,帮助读者避免常见的配置陷阱。通过这些经验分享,希望读者能够更加顺利地完成Hadoop集群的搭建和配置。 ... [详细]
  • Hadoop 2.6 主要由 HDFS 和 YARN 两大部分组成,其中 YARN 包含了运行在 ResourceManager 的 JVM 中的组件以及在 NodeManager 中运行的部分。本文深入探讨了 Hadoop 2.6 日志文件的解析方法,并详细介绍了 MapReduce 日志管理的最佳实践,旨在帮助用户更好地理解和优化日志处理流程,提高系统运维效率。 ... [详细]
  • 在数据表中,我需要触发一个操作来刷新特定列的数据。例如,对于以下表格:| ID | Name | IsDeleted ||----|-------|-----------|| 1 | test | True || 2 | test2 | False |我希望在点击“更新”按钮时,能够仅刷新选定行的“IsDeleted”列。这将有助于确保数据的实时性和准确性。 ... [详细]
  • 如果程序使用Go语言编写并涉及单向或双向TLS认证,可能会遭受CPU拒绝服务攻击(DoS)。本文深入分析了CVE-2018-16875漏洞,探讨其成因、影响及防范措施,为开发者提供全面的安全指导。 ... [详细]
  • 本文详细探讨了MySQL并发参数的优化与调整方法,旨在帮助读者深入了解如何通过合理配置这些参数来提升数据库性能。文章不仅介绍了常见的并发参数及其作用,还提供了实际操作中的调整策略和最佳实践,适合希望提高数据库管理技能的技术人员阅读。 ... [详细]
  • 构建高可用性Spark分布式集群:大数据环境下的最佳实践
    在构建高可用性的Spark分布式集群过程中,确保所有节点之间的无密码登录是至关重要的一步。通过在每个节点上生成SSH密钥对(使用 `ssh-keygen -t rsa` 命令并保持默认设置),可以实现这一目标。此外,还需将生成的公钥分发到所有节点的 `~/.ssh/authorized_keys` 文件中,以确保节点间的无缝通信。为了进一步提升集群的稳定性和性能,建议采用负载均衡和故障恢复机制,并定期进行系统监控和维护。 ... [详细]
  • 本文深入探讨了 hCalendar 微格式在事件与时间、地点相关活动标记中的应用。作为微格式系列文章的第四篇,前文已分别介绍了 rel 属性用于定义链接关系、XFN 微格式增强链接的人际关系描述以及 hCard 微格式对个人和组织信息的描述。本次将重点解析 hCalendar 如何通过结构化数据标记,提高事件信息的可读性和互操作性。 ... [详细]
  • 【漫画解析】数据已删,存储空间为何未减?揭秘背后真相
    在数据迁移过程中,即使删除了原有数据,存储空间却未必会相应减少。本文通过漫画形式解析了这一现象背后的真相。具体来说,使用 `mysqldump` 命令进行数据导出时,该工具作为 MySQL 的逻辑备份工具,通过连接数据库并查询所需数据,将其转换为 SQL 语句。然而,这种操作并不会立即释放存储空间,因为数据库系统可能保留了已删除数据的碎片信息。文章进一步探讨了如何优化存储管理,以确保数据删除后能够有效回收存储空间。 ... [详细]
  • CentOS 7环境下Jenkins的安装与前后端应用部署详解
    CentOS 7环境下Jenkins的安装与前后端应用部署详解 ... [详细]
  • 基于OpenCV的图像拼接技术实践与示例代码解析
    图像拼接技术在全景摄影中具有广泛应用,如手机全景拍摄功能,通过将多张照片根据其关联信息合成为一张完整图像。本文详细探讨了使用Python和OpenCV库实现图像拼接的具体方法,并提供了示例代码解析,帮助读者深入理解该技术的实现过程。 ... [详细]
  • 公司计划部署邮件服务器,考虑到已有域名,决定自行搭建内部邮件服务器。经过综合考量,最终选择在Linux环境中进行搭建,并记录了相关配置和实践过程。本文将详细介绍Postfix的基本设置步骤和实践经验,帮助读者快速掌握邮件服务器的搭建方法。 ... [详细]
  • 解决MySQL 5.1服务器无法正确识别中文字符的问题
    在使用MySQL 5.1服务器时,可能会遇到无法正确识别中文字符的问题。由于相关资料较少且不够全面,本文将详细阐述解决方案。首先,需要检查MySQL的配置文件,确保字符集设置正确,并通过命令行工具验证当前的字符编码配置。此外,建议更新到最新版本以避免此类问题。 ... [详细]
author-avatar
彼岸花芬芳
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有