热门标签 | 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。



推荐阅读
  • 本文详细探讨了C语言中`extern`关键字的简易编译方法,并深入解析了预编译、`static`和`extern`的综合应用。通过具体的代码示例,介绍了如何在不同的文件之间共享变量和函数声明,以及这些关键字在编译过程中的作用和影响。文章还讨论了预编译过程中宏定义的使用,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • Java 零基础入门:SQL Server 学习笔记(第21篇)
    Java 零基础入门:SQL Server 学习笔记(第21篇) ... [详细]
  • 如何在Java中高效构建WebService
    本文介绍了如何利用XFire框架在Java中高效构建WebService。XFire是一个轻量级、高性能的Java SOAP框架,能够简化WebService的开发流程。通过结合MyEclipse集成开发环境,开发者可以更便捷地进行项目配置和代码编写,从而提高开发效率。此外,文章还详细探讨了XFire的关键特性和最佳实践,为读者提供了实用的参考。 ... [详细]
  • 题目描述:小K不幸被LL邪教洗脑,洗脑程度之深使他决定彻底脱离这个邪教。在最终离开前,他计划再进行一次亚瑟王游戏。作为最后一战,他希望这次游戏能够尽善尽美。众所周知,亚瑟王游戏的结果很大程度上取决于运气,但通过合理的策略和算法优化,可以提高获胜的概率。本文将详细解析洛谷P3239 [HNOI2015] 亚瑟王问题,并提供具体的算法实现方法,帮助读者更好地理解和应用相关技术。 ... [详细]
  • Go语言实现Redis客户端与服务器的交互机制深入解析
    在前文对Godis v1.0版本的基础功能进行了详细介绍后,本文将重点探讨如何实现客户端与服务器之间的交互机制。通过具体代码实现,使客户端与服务器能够顺利通信,赋予项目实际运行的能力。本文将详细解析Go语言在实现这一过程中的关键技术和实现细节,帮助读者深入了解Redis客户端与服务器的交互原理。 ... [详细]
  • 本文深入探讨了 Python Watchdog 库的使用方法和应用场景。通过详细的代码示例,展示了如何利用 Watchdog 监控文件系统的变化,包括文件的创建、修改和删除等操作。文章不仅介绍了 Watchdog 的基本功能,还探讨了其在实际项目中的高级应用,如日志监控和自动化任务触发。读者将能够全面了解 Watchdog 的工作原理及其在不同场景下的应用技巧。 ... [详细]
  • MySQL:不仅仅是数据库那么简单
    MySQL不仅是一款高效、可靠的数据库管理系统,它还具备丰富的功能和扩展性,支持多种存储引擎,适用于各种应用场景。从简单的网站开发到复杂的企业级应用,MySQL都能提供强大的数据管理和优化能力,满足不同用户的需求。其开源特性也促进了社区的活跃发展,为技术进步提供了持续动力。 ... [详细]
  • voc生成xml 代码
    目录 lxmlwindows安装 读取示例 可视化 生成示例 上面是代码,下面有调用示例 api调用代码,其实只有几行:这个生成代码也很简 ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
  • 尽管存在唯一列,仍显示“当前选择不包含唯一列。网格编辑、复选框、编辑、复制和删除功能不可用”的消息。 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • Django框架下的对象关系映射(ORM)详解
    在Django框架中,对象关系映射(ORM)技术是解决面向对象编程与关系型数据库之间不兼容问题的关键工具。通过将数据库表结构映射到Python类,ORM使得开发者能够以面向对象的方式操作数据库,从而简化了数据访问和管理的复杂性。这种技术不仅提高了代码的可读性和可维护性,还增强了应用程序的灵活性和扩展性。 ... [详细]
  • 为了优化直播应用底部聊天框的弹出机制,确保在不同设备上的布局稳定性和兼容性,特别是在配备虚拟按键的设备上,我们对用户交互流程进行了调整。首次打开应用时,需先点击首个输入框以准确获取键盘高度,避免直接点击第二个输入框导致的整体布局挤压问题。此优化通过调整 `activity_main.xml` 布局文件实现,确保了更好的用户体验和界面适配。 ... [详细]
  • MySQL 数据操作:增、删、查、改全面解析
    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社区 版权所有