监控MySQL数据表变化:Binlog的重要性及实践

news2024/12/22 1:55:22

引言

在数据库管理领域,跟踪和记录对数据库所做的更改是至关重要的。这些更改包括插入、更新或删除操作,它们可以影响应用程序的行为、数据一致性和安全性。MySQL 提供了多种机制来追踪这些变化,其中最强大且灵活的一种就是使用二进制日志(Binary Log),简称 binlog。

Binlog概述

从 MySQL 8.0 开始,默认情况下启用了二进制日志功能。而在早期版本如 MySQL 5.* 中,则需要手动开启。Binlog 是一种事务安全的日志文件,它记录了所有对数据库结构和内容进行修改的操作。通过这个日志,我们可以恢复到之前的某个状态,这对于灾难恢复、复制以及审计都是非常有用的工具。

监控数据表变化的意义

数据恢复与备份:当意外发生时,比如误删了一张重要表格,binlog 可以帮助我们回滚到事故发生前的状态。
主从复制:MySQL 的主从架构依赖于 binlog 来同步主服务器上的变更到从服务器。
审计和合规性:企业环境中,了解谁做了什么改动对于满足法规要求非常重要。
性能优化:分析频繁变更的数据可以帮助识别热点数据区域,从而优化索引或调整应用逻辑。
实时数据分析:一些系统利用 binlog 实现实时的数据流处理,例如将变更事件发送给消息队列进行进一步处理。

如何启用Binlog

对于 MySQL 5.* 版本,如果想要开启 binlog,你需要编辑配置文件 my.cnf 或者 my.ini,并在 [mysqld] 部分添加如下配置:

[mysqld]
log-bin=mysql-bin
server-id=1

然后重启 MySQL 服务使设置生效。

示例代码:读取Binlog

以下是一个简单的 java 代码片段,演示了如何使用 mysql-replication 库读取 MySQL 的 binlog 文件。
依赖

<dependency>
            <groupId>com.zendesk</groupId>
            <artifactId>mysql-binlog-connector-java</artifactId>
            <version>0.30.1</version>
        </dependency>

代码示例


import cn.com.yeexun.core.exception.BizException;
import com.github.shyiko.mysql.binlog.BinaryLogClient;
import com.github.shyiko.mysql.binlog.event.Event;
import com.github.shyiko.mysql.binlog.event.EventData;
import com.github.shyiko.mysql.binlog.event.TableMapEventData;
import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData;
import com.github.shyiko.mysql.binlog.event.DeleteRowsEventData;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.sql.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class BinlogListenerService {

    private static final String HOST = "真实ip";
    private static final int PORT = 3306;
    private static final String USERNAME = "root";
    private static final String PASSWORD = "密码";

    // 记录表 ID 和表名的映射关系
    private final Map<Long, String> tableIdToNameMap = new HashMap<>();
    // 监控数据库
    private static final String TARGET_DATABASE = "dn_name";
    // 监控目标表
    private static final String[] TARGET_TABLES = {"table_name1", "table_name2"};

    @PostConstruct
    public void startListening() {
        new Thread(() -> {
            try {
                BinaryLogClient client = new BinaryLogClient(HOST, PORT, USERNAME, PASSWORD);
                client.registerEventListener(this::handleEvent);
                client.connect();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void handleEvent(Event event) {
        EventData data = event.getData();
        if (data instanceof TableMapEventData) {
            // 捕获表信息
            TableMapEventData tableMapEventData = (TableMapEventData) data;
            String tableName = tableMapEventData.getTable();
            String databaseName = tableMapEventData.getDatabase();
            // 记录表 ID 和表名映射
            tableIdToNameMap.put(tableMapEventData.getTableId(), databaseName + "." + tableName);
        } else if (data instanceof WriteRowsEventData) {
            WriteRowsEventData eventData = (WriteRowsEventData) data;
            processEvent(eventData.getTableId(), "INSERT", eventData);
        } else if (data instanceof UpdateRowsEventData) {
            UpdateRowsEventData eventData = (UpdateRowsEventData) data;
            processEvent(eventData.getTableId(), "UPDATE", eventData);
        } else if (data instanceof DeleteRowsEventData) {
            DeleteRowsEventData eventData = (DeleteRowsEventData) data;
            processEvent(eventData.getTableId(), "DELETE", eventData);
        }
    }

    private void processEvent(long tableId, String eventType, Object eventData) {
        // 根据 tableId 获取表名
        String fullTableName = tableIdToNameMap.get(tableId);
        if (fullTableName == null) {
            return; // 未记录的表,忽略
        }
        String[] parts = fullTableName.split("\\.");
        String databaseName = parts[0];
        String tableName = parts[1];
        // 检查是否是目标库和表
        if (TARGET_DATABASE.equals(databaseName) && isTargetTable(tableName)) {
            if (eventType.contains("UPDATE")) {
                System.out.println("捕获到 " + eventType + " 事件 - 数据库: " + databaseName + ", 表: " + tableName);
                System.out.println("事件数据: " + eventData);
                String ss = eventData.toString();
                System.out.println("表名称" + tableName);
                Pattern rowsPattern = Pattern.compile("before=\\[(.*?)\\], after=\\[(.*?)\\]");
                Matcher rowsMatcher = rowsPattern.matcher(ss);
                while (rowsMatcher.find()) {
                    String beforeData = rowsMatcher.group(1);
                    String afterData = rowsMatcher.group(2);
                    System.out.println("变更前: " + beforeData);
                    System.out.println("变更后: " + afterData);
                    List<String> beforeList = Arrays.asList(beforeData.split(",\\s*"));
                    List<String> afterList = Arrays.asList(afterData.split(",\\s*"));
                    compareDifferences(beforeList, afterList, tableName);
                }
            } else {
//                // 新增和删除的数据
                String ss = eventData.toString();
                System.out.println(eventType.contains("INSERT") ? "新增" : "删除");
                Pattern rowsPattern = Pattern.compile("rows=\\[(.*?)\\]\\}", Pattern.DOTALL); // 捕获 rows 部分
                Matcher rowsMatcher = rowsPattern.matcher(ss);

                if (rowsMatcher.find()) {
                    String rowsContent = rowsMatcher.group(1).trim(); // 提取 rows 的内容
                    //System.out.println("数据行:\n" + rowsContent);

                    // 匹配每一行数据
                    Pattern rowPattern = Pattern.compile("\\[([^\\]]+)]"); // 捕获 [] 中的内容
                    Matcher rowMatcher = rowPattern.matcher(rowsContent);

                    while (rowMatcher.find()) {
                        String rowData = rowMatcher.group(1).trim(); // 提取每一行数据
                        System.out.println("数据行: " + rowData);

                    }
                } else {
                    System.out.println("未匹配到 rows 数据!");
                }

            }
        }
    }

    public static void compareDifferences(List<String> before, List<String> after, String tableName) {
        List<String> columnNamesFromDatabase = getColumnNamesFromDatabase(tableName);
        for (int i = 0; i < before.size(); i++) {
            String beforeValue = before.get(i);
            String afterValue = after.get(i);
            if (!Objects.equals(beforeValue, afterValue)) {

                System.out.printf("列名 %s: Before = %s, After = %s%n", columnNamesFromDatabase.get(i), beforeValue, afterValue);
            }
        }
    }

    private boolean isTargetTable(String tableName) {
        for (String targetTable : TARGET_TABLES) {
            if (targetTable.equals(tableName)) {
                return true;
            }
        }
        return false;
    }

    public static List<String> getColumnNamesFromDatabase(String tableName) {
        List<String> columnNames = new ArrayList<>();
        String url = "jdbc:mysql://" + HOST + ":" + PORT + "/" + TARGET_DATABASE;
        String username = USERNAME;
        String password = PASSWORD;

        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            DatabaseMetaData metaData = connection.getMetaData();
            ResultSet columns = metaData.getColumns(null, null, tableName, null);

            while (columns.next()) {
                String columnName = columns.getString("COLUMN_NAME");
                columnNames.add(columnName);
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new BizException("获取数据表元数据信息出错");
        }
        return columnNames;
    }

}


这段代码会持续监听 MySQL 的 binlog,并打印出所有的删除、更新和插入事件。请注意,这只是一个基础的例子,在生产环境中使用时,可能还需要考虑更多因素,如错误处理、多线程支持等。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp 自定义图标03

插入工程&#xff0c;修改名称文件内容 编译运行

在Windows本地用网页查看编辑服务器上的 jupyter notebook

​ Motivation: jupyter notebook 可以存中间变量&#xff0c;方便我调整代码&#xff0c;但是怎么用服务器的GPU并在网页上查看编辑呢&#xff1f; 参考 https://zhuanlan.zhihu.com/p/440080687 服务端(Ubuntu)&#xff1a; 激活环境 source activate my_env安装notebook …

【YOLO 项目实战】(11)YOLO8 数据集与模型训练

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【YOLO 项目实战】&#xff08;1&#xff09;YOLO5 环境配置与检测 【YOLO 项目实战】&#xff08;10&#xff09;YOLO8 环境配置与推理检测 【YOLO 项目实战】&#xff08;11&#xff09;YOLO8 数据…

Ubuntu22.04上安装esp-idf

一、安装准备# 建议使用Ubuntu 20.04 或 Ubuntu 22.04 操作系统 为了在 Ubuntu 22.04 中使用 esp-idf&#xff0c;需要安装一些依赖包 sudo apt-get install git wget flex bison gperf python3\python3-pip python3-venv cmake ninja-build ccache\libffi-dev libssl-dev dfu…

nginx-虚拟主机配置笔记

目录 nginx的安装可以查看nginx安装https://blog.csdn.net/m0_68472908/article/details/144609023?spm1001.2014.3001.5501 一、 基于域名 二、 基于IP 三、 基于端口 nginx的安装可以查看nginx安装https://blog.csdn.net/m0_68472908/article/details/144609023?spm100…

AlipayHK支付宝HK接入-商户收款(PHP)

一打开支付宝国际版 二、点开商户服务 三、下载源码

Soul Android端稳定性背后的那些事

前言&#xff1a;移动应用的稳定性对于用户体验和产品商业价值都有着至关重要的作用。应用崩溃会导致关键业务中断、用户留存率下降、品牌口碑变差、生命周期价值下降等影响&#xff0c;甚至会导致用户流失。因此&#xff0c;稳定性是APP质量构建体系中最基本和最关键的一环。当…

深度学习模型 DeepSeek-VL2 及其消费级显卡需求分析

DeepSeek-VL2 是由 DeepSeek 团队开发的一款先进的视觉语言模型&#xff0c;采用了混合专家&#xff08;MoE&#xff09;架构&#xff0c;旨在提升多模态理解能力。该模型包括三个版本&#xff1a;DeepSeek-VL2-Tiny、DeepSeek-VL2-Small 和 DeepSeek-VL2。每个版本具有不同的模…

首批|云轴科技ZStack成为开放智算产业联盟首批会员单位

近日 &#xff0c;在Linux基金会AI & Data及中国开源软件推进联盟的指导之下&#xff0c;开放智算产业联盟成立大会在北京成功召开。在大会上&#xff0c;联盟首次公布了组织架构并颁发了首批会员单位证书。凭借ZStack AIOS平台智塔和在智算领域的技术创新&#xff0c;云轴…

word实现两栏格式公式居中,编号右对齐

1、确定分栏的宽度 选定一段文字 点击分栏&#xff1a;如本文的宽度为22.08字符 2、将公式设置为 两端对齐&#xff0c;首行无缩进。 将光标放在 公式前面 点击 格式-->段落-->制表位 在“制表位位置”输入-->11.04字符&#xff08;22.08/211.04字符&#xff09;&…

go语言zero框架中config读取不到.env文件问题排查与解决方案

在Go语言中&#xff0c;如果你使用.env文件来存储环境变量&#xff0c;通常会用到一些第三方库&#xff0c;例如github.com/joho/godotenv&#xff0c;它可以帮助我们从.env文件中读取环境变量。然而&#xff0c;在使用godotenv时&#xff0c;可能会遇到一些问题&#xff0c;导…

修改vscode设置的原理

转载请标明出处&#xff1a;小帆的帆的专栏 修改vscode设置 首先需要理解的是&#xff0c;vscode的系统设置和插件设置都是通过settings.json文件管理的。 vscode中有三个Settings&#xff0c;三个Settings分别对应三个settings.json文件 Default Settings&#xff1a;默认…

Qt之修改窗口标题、图标以及自定义标题栏(九)

Qt开发 系列文章 - titles-icons-titlebars&#xff08;九&#xff09; 目录 前言 一、修改标题 二、添加图标 三、更换标题栏 1.效果演示 2.创建标题栏类 3.定义相关函数 4.使用标题栏类 总结 前言 在我们利用Qt设计软件时&#xff0c;经常需要修改窗口标题、更改软…

环境变量的知识

目录 1. 环境变量的概念 2. 命令行参数 2.1 2.2 创建 code.c 文件 2.3 对比 ./code 执行和直接 code 执行 2.4 怎么可以不带 ./ 2.4.1 把我们的二进制文件拷贝到 usr/bin 路径下&#xff0c;也不用带 ./ 了 2.4.2 把我们自己的路径添加到环境变量里 3. 认识PATH 3.…

【时时三省】(C语言基础)通讯录1

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 通讯录 1 .通讯录中能够存放1000个人的信息 每个人的信息&#xff1a; 名字年龄性别电话地址 2、增加人的信息 3、删除指定人的信息 4、修改指定人的信息 5&#xff0c;查找指定人的信…

Vulhub:Redis[漏洞复现]

4-unacc(Redis未授权代码执行) 启动漏洞环境 docker-compose up -d 阅读vulhub给出的漏洞文档 cat README.zh-cn.md # Redis 4.x/5.x 主从复制导致的命令执行 Redis是著名的开源Key-Value数据库&#xff0c;其具备在沙箱中执行Lua脚本的能力。 Redis未授权访问在4.x/5.0.5以…

【PGCCC】Postgresql Varlena 结构

前言 postgresql 会有一些变长的数据类型&#xff0c;存储都是采用 varlena 格式的&#xff08;除了 cstring 类型&#xff09;&#xff0c;通过语句 SELECT typname FROM pg_type WHERE typlen -1就可以看到所有采用 varlena 格式的数据类型&#xff0c;比如常见的 text &am…

Ubuntu搭建ES8集群+加密通讯+https访问

目录 写在前面 一、前期准备 1. 创建用户和用户组 2. 修改limits.conf文件 3. 关闭操作系统swap功能 4. 调整mmap上限 二、安装ES 1.下载ES 2.配置集群间安全访问证书密钥 3.配置elasticsearch.yml 4.修改jvm.options 5.启动ES服务 6.修改密码 7.启用外部ht…

LeetCode:144.前序遍历

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;144. 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#x…

git remote -v(--verbose)显示你的 Git 仓库配置的远程仓库的详细信息

git remote -v 是一个 Git 命令&#xff0c;用于显示你的 Git 仓库配置的远程仓库的详细信息。 当你执行 git remote -v 命令时&#xff0c;你会看到类似以下的输出&#xff1a; origin https://github.com/your-username/your-repo.git (fetch) origin https://github.com…