MySQL 如何从 Binlog 找出变更记录并回滚

文章目录

    • 前言
    • 1. 案例模拟
      • 1.1 确认信息
      • 1.2 下载 Binlog
      • 1.3 准备环境
      • 1.4 注册 Binlog
      • 1.5 准备结构信息
      • 1.6 Python 订阅
      • 1.7 输出结果展示
    • 2. 原理解析
      • 2.1 程序设计
      • 2.2 模块版本
    • 总结

前言

最近有研发同学问我:有一个问题,想查一个 ID 为 xxxx 的 sku 什么时候被更新了吗?更新前的数据是什么?一般这么讲,可能是由于手动执行或者是代码 Bug 导致出现数据丢失或者数据误更新,需要确认订正,一般需要通过分析 Binlog 来解决,本篇文章将通过该案例介绍此类问题的处理思路。

1. 案例模拟

1.1 确认信息

当有需求需要从 Binlog 中查询变更记录或者需要闪回数据的时候,需要和研发确认 时间范围、涉及到的 环境信息、库名、表名 最好是可以提供 SQL 语句。在上述的案例中,研发提供的是 sku 的 ID 时间范围是 2024-02-22 18:01:42 ~ 18:03:42。

1.2 下载 Binlog

阿里云、腾讯云、华为云 的数据库服务 Binlog 都是支持直接下载的,按照研发提供的时间区间下载对应的 Binlog 日志。
在这里插入图片描述
如果是本地自建的 MySQL 数据库,是没用日志开始时间和日志结束时间的,需要先确认下时间。可参考下方文档。

推荐阅读:MySQL 查询 Binlog 生成时间

1.3 准备环境

Binlog 下载完成后,那我们想要的数据记录也在里面,接下来将介绍如何获得我们想要的记录,首先需要准备一台测试数据库(作为数据库管理人员,随身带一套 MySQL 测试环境不过分吧?)测试数据库的大版本需要和生产环境的版本大版本一致。

以下是我环境信息:

  • 生产环境 MySQL 5.7.18
  • 测试环境 MySQL 5.7.33 (单实例)

1.4 注册 Binlog

该步骤,需要把从云上下载的 Binlog 注册到我们的测试环境中,首先需要先清空测试环境中的 Binlog 日志。

reset master;

查询 Binlog 索引文件的位置:

show variables like 'log_bin_index';

将我们从生产环境下载的 Binlog 拷贝到测试环境 Binlog 目录,然后再按照 mysql-bin.index 文件中的格式,将 Binlog 写进去。

/data/mysql_57/logs/mysql-bin.000001
/data/mysql_57/logs/mysql-bin.000002
/data/mysql_57/logs/mysql-bin.000003
/data/mysql_57/logs/mysql-bin.000004
/data/mysql_57/logs/mysql-bin.000005

上面,是注册完成的 Binlog 索引文件信息,生产环境下载了 5 个 Binlog 他们分别是 008213、008214、008215、008216、008217,拷贝到测试环境后,我们将原来 Binlog 名字修改为从 000001 开始,并且是顺序的。注意给拷贝来的 Binlog 设置用户属组。

chown -R mysql:mysql mysql-bin.*

设置完成后,重启测试环境的数据库,注册阶段完成。

1.5 准备结构信息

该步骤,需要把生产环境的表结构 copy 一份到测试环境。不用全部 copy 只 copy 需要查询记录的表。例如上面的 case 我们要查 product 库下的 sku 表。就在测试环境创建一个 product 库,然后将生产环境 sku 的表结构 copy 到测试环境。

create database product;
use product;-- 不在此展示完成结构了,与生产环境保持一致就行
create table sku(.........)create table sku_price(.........)

需要查询到记录涉及到多少张表,那么就 copy 多少张表就行。这次案例涉及到 2 张表。

1.6 Python 订阅

该步骤,要从 5 个 Binlog 文件中搜索到我们想要的记录,一个 Binlog 中可能有几十万个事务,这里我们通过编写 Python 脚本简化操作。我们要搜索的是 product 库下 sku、sku_price 表 sku_id = 810827 的变更记录,只需要按照下方代码注释修改即可。

在准备结构信息的步骤中,我们只在注册服务器中创建了需要的表,就起到了过滤表的作用,所以代码中不需要指定表名。

# -*- coding: utf-8 -*-
import sys
from datetime import datetime
from decimal import Decimal
from pymysqlreplication import BinLogStreamReader
from pymysqlreplication.row_event import (DeleteRowsEvent,UpdateRowsEvent,WriteRowsEvent
)
from pymysqlreplication.event import XidEvent, QueryEvent# 填写注册 MySQL 连接信息
mysql_settings = {'host': '172.16.104.56','port': 3306,'user': 'bing','password': 'abc123'
}stream = BinLogStreamReader(connection_settings=mysql_settings,server_id=8023,log_file='mysql-bin.000001',  # 从哪个 Binlog 开始扫描log_pos=4,only_schemas='product',  # 数据库名称only_events=[DeleteRowsEvent,UpdateRowsEvent,WriteRowsEvent,]
)def simple_data_type(data_info: dict):"""直接打印结果会包含一些对象信息,在这里简化处理"""tem_data = {}for key, value in data_info.items():if isinstance(value, Decimal):tem_data[key] = float(value)elif isinstance(value, datetime):tem_data[key] = value.strftime('%Y-%m-%d %H:%M:%S')else:tem_data[key] = valuereturn tem_datasearch_file_name = Nonefor binlog_event in stream:if search_file_name == stream.log_file:passelif search_file_name != stream.log_file:search_file_name = stream.log_fileprint('正在扫描:', search_file_name)for row in binlog_event.rows:try:event_time = datetime.fromtimestamp(binlog_event.timestamp)except OSError:event_time = datetime(1980, 1, 1, 0, 0)if isinstance(binlog_event, DeleteRowsEvent):df = row["values"]# 这里条件,需要自己改if int(df['sku_id']) == 810887:print('-' * 160)print('操作类型: DELETE')print('时间: ', event_time)print('日志文件: ', stream.log_file)print('数据库名:', binlog_event.schema)print('表名:', binlog_event.table)print('Position: ', binlog_event.packet.log_pos)print(simple_data_type(df))print('-' * 160)elif isinstance(binlog_event, UpdateRowsEvent):df = row["before_values"]# 这里条件,需要自己改if int(df['sku_id']) == 810827:print('-' * 160)print('操作类型: UPDATE')print('时间: ', event_time)print('日志文件: ', stream.log_file)print('数据库名:', binlog_event.schema)print('表名:', binlog_event.table)print('Position: ', binlog_event.packet.log_pos)print('before_values: ', simple_data_type(row["before_values"]))print('after_values: ', simple_data_type(row["after_values"]))elif isinstance(binlog_event, WriteRowsEvent):df = row["values"]# 这里条件,需要自己改if int(df['sku_id']) == 810827:print('-' * 160)print('操作类型: INSERT')print('时间: ', event_time)print('日志文件: ', stream.log_file)print('数据库名:', binlog_event.schema)print('表名:', binlog_event.table)print('Position: ', binlog_event.packet.log_pos)print(simple_data_type(df))

1.7 输出结果展示

结果已脱敏,可以看出 boutique_price 从原来的 1058.46 被修改为 1614.0,需要注意的是 Binlog 中的 Event 只能精确到秒。

操作类型: UPDATE
时间:  2024-02-22 18:02:42
日志文件:  mysql-bin.000003
数据库名: product
表名: sku
Position:  65716973
before_values:  {'sku_id': 810887, 'product_id': 26492, 'sku_code': '000', 'name': '', 'coverpic': '', 'introduction': '', 'in_price': 132.31, 'price': 361.1, 'created_at': '2022-11-18 13:37:48', 'updated_at': '2024-02-21 04:10:41', 'enabled': '1', 'retail_price': None, 'im_price': 150.0, 'last_check': '2022-11-18 13:37:48', 'size': 'UNI', 'boutique_price': 1058.46}
after_values:  {'sku_id': 810887, 'product_id': 26492, 'sku_code': '000', 'name': '', 'coverpic': '', 'introduction': '', 'in_price': 132.31, 'price': 361.1, 'created_at': '2022-11-18 13:37:48', 'updated_at': '2024-02-22 18:02:42', 'enabled': '1', 'retail_price': None, 'im_price': 150.0, 'last_check': '2022-11-18 13:37:48', 'size': 'UNI', 'boutique_price': 1614.0}

将结果交给研发,任务就算完成了。

2. 原理解析

2.1 程序设计

这里用到了一个模块 pymysqlreplication 它可以伪装成一个 IO 复制线程,从 MySQL 服务器中拉取 Binlog Event 并支持解析。

为什么直接解析 Binlog?因为 Binlog 中没用表字段名信息,直接解析比较难做一些过滤操作。先将表结构和 Binlog 注册到一台测试 MySQL 服务器,然后通过伪装 IO 复制线程拉取 Event 过滤找到我们想要的记录。

2.2 模块版本

模块代码库:python-mysql-replication

# 本次实验使用的版本
mysql-replication==0.13

安装方法:

pip3 install mysql-replication

总结

本篇文章介绍了如何从 Binlog 中定位记录,需要有一点 Python 基础,但注册 Binlog 思路可应用多个场景,例如使用它恢复增量日志等。得到记录结果后,如果要回滚,那么可以依靠上面的字典中的信息,翻译成 SQL 语句即可,目前程序还没有实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/266121.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Gradle开发与应用 (二) : Groovy基础语法

1. Groovy是什么 Groovy是基于JVM虚拟机的一种动态语言,语法和Java非常相似,并能够无缝地与Java代码集成和互操作,增加了很多动态类型和灵活的特性。(闭包、DSL) 语法和Java非常相似这个特点,意味着,如果我们完全不懂…

推荐几款优秀免费开源的导航网站

🦩van-nav 项目地址:van-nav项目介绍:一个轻量导航站,汇总你的所有服务。项目亮点:全平台支持,单文件部署,有配套浏览器插件。效果预览 🦩发现导航 项目地址:nav项目…

算法 -【从前序与中序遍历序列构造二叉树】

从前序与中序遍历序列构造二叉树 题目示例1示例2 分析代码 题目 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。 示例1 输入: preorder [3,9,20,1…

RabbitMQ实战学习

RabbitMQ实战学习 文章目录 RabbitMQ实战学习RabbitMQ常用资料1、安装教程2、使用安装包3、常用命令4、验证访问5、代码示例 一、RabbitMQ基本概念1.1. MQ概述1.2 MQ 的优势和劣势1.3 MQ 的优势1. 应用解耦2. 异步提速3. 削峰填谷 1.4 MQ 的劣势1.5 RabbitMQ 基础架构1.6 JMS 二…

怎样才能考上南京大学的计算机研究生?

附上南大与同层次学校近四年的分数线对比,整体很难 添加图片注释,不超过 140 字(可选) 添加图片注释,不超过 140 字(可选) 我本人是双非一本的计算机专业,23考研一战上岸的&#xf…

【探索AI】探索未来-计算机专业必看的几部电影

计算机专业必看的几部电影 计算机专业必看的几部电影,就像一场精彩的编程盛宴!《黑客帝国》让你穿越虚拟世界,感受高科技的魅力;《社交网络》揭示了互联网巨头的创业之路,《源代码》带你穿越时间解救世界,…

我写了个ImageWindow应用

文章目录 0 引言1 应用简介2 主要功能和特点2.1 多图像同/异步像素级对比2.2 支持多达30种图像格式2.3 高效率的图像处理性能 3 简明使用教程3.1 软件下载安装与更新3.1.1 软件下载与安装3.1.2 软件更新 3.2 多视窗添加并自动最优排列3.3 多样化图像导入方式3.4 自动切换显示模…

MS1100——16-bit 内置基准模数转换器,可替代ADS1100

产品简述 MS1100 是一款高精度 16bit 模数转换器。内部集成 2.048V 基 准源,差分输入范围达到 2.048V 。使用了 I 2 C 兼容接口。电源电 压范围为 2.7V 到 5.5V 。 MS1100 转换速率为 15 、 30 、 60 或 240SPS ,集成有可编程增 益放…

【Web安全靶场】sqli-labs-master 21-37 Advanced-Injection

sqli-labs-master 21-37 Advanced-Injection 第一关到第二十关请见专栏 文章目录 sqli-labs-master 21-37 Advanced-Injection第二十一关-Cookie注入第二十二关-Cookie注入第二十三关-注释符过滤的报错注入第二十四关-二次注入第二十五关-过滤OR、AND双写绕过第二十五a关-过滤…

如何在Window系统部署BUG管理软件并结合内网穿透实现远程管理本地BUG

文章目录 前言1. 本地安装配置BUG管理系统2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射本地服务3. 测试公网远程访问4. 配置固定二级子域名4.1 保留一个二级子域名5.1 配置二级子域名6. 使用固定二级子域名远程 前言 BUG管理软件,作为软件测试工程师的必备工具之一。在…

数据结构--树的遍历

数据结构--树的遍历 1. 前序中序后序遍历2. 前序中序后序遍历代码 1. 前序中序后序遍历 2. 前序中序后序遍历代码 /** public class TreeNode {int val 0;TreeNode left null;TreeNode right null;public TreeNode(int val) {this.val val;}} */// 前序遍历顺序&#xff1…

vue中使用echarts绘制双Y轴图表时,刻度没有对齐的两种解决方法

文章目录 1、原因2、思路3、解决方法3.1、使用alignTicks解决3.2、结合min和max属性去配置interval属性1、首先固定两边的分隔的段数。2、结合min和max属性去配置interval。 1、原因 刻度在显示时,分割段数不一样,导致左右的刻度线不一致,不…

GPT 的基础 - T(Transformer)

我们知道GPT的含义是: Generative - 生成下一个词 Pre-trained - 文本预训练 Transformer - 基于Transformer架构 我们看到Transformer模型是GPT的基础,这篇博客梳理了一下Transformer的知识点。 BERT: 用于语言理解。(Transformer的Encoder…

Redis 在 Linux 系统下安装部署的两种方式详细说明

小伙伴们好,欢迎关注,一起学习,无限进步 Redis安装和配置 1、首先在官网下载好redis-6.0.9.tar.gzhttp://redis.io/ 或者使用 wget 命令下载:wget http://download.redis.io/releases/redis-6.0.9.tar.gz 2、下载使用上传到阿里…

vue使用gitshot生成gif

vue使用gitshot生成gif 问题背景 本文将介绍vue中使用gitshot生成gif。 问题分析 解决思路: 使用input组件上传一个视频,获取视频文件后用一个video组件进行播放,播放过程进行截图生成图片数组。 demo演示上传一个视频,然后生…

【InternLM 实战营笔记】大模型评测

随着人工智能技术的快速发展, 大规模预训练自然语言模型成为了研究热点和关注焦点。OpenAI于2018年提出了第一代GPT模型,开辟了自然语言模型生成式预训练的路线。沿着这条路线,随后又陆续发布了GPT-2和GPT-3模型。与此同时,谷歌也…

微服务之qiankun主项目+子项目搭建

主项目使用history,子项目使用hash模式 1. 下载安装"qiankun": "^2.10.13"2. 手动调用qiankun,使用vue脚手架搭建的项目1. 主项目配置(我使用的是手动调用乾坤,在指定页面显示内容)1. 要使用的页面中引入乾坤…

微服务学习

一、服务注册发现 服务注册就是维护一个登记簿,它管理系统内所有的服务地址。当新的服务启动后,它会向登记簿交待自己的地址信息。服务的依赖方直接向登记簿要Service Provider地址就行了。当下用于服务注册的工具非常多ZooKeeper,Consul&am…

使用 Gradle 版本目录进行依赖管理 - Android

/ 前言 / 在软件开发中,依赖管理是一个至关重要的方面。合理的依赖版本控制有助于确保项目的稳定性、安全性和可维护性。 Gradle版本目录(Version Catalogs)是 Gradle 构建工具的一个强大功能,它为项目提供了一种集中管理依赖…

TSINGSEE青犀AI智能分析网关V4区域入侵检测算法及应用介绍

区域入侵检测算法主要应用于需要高度安全防护的场所,如:电力、水利、石油等国家基础设施场所;政府机关、军事基地等重要设施;监狱、看守所等监管场所;大型企业、工厂等生产区域;校园、住宅小区、楼宇等。这…