MySQL高级之备份与还原

一、备份还原

1、 为什么要进行 MySQL 备份与还原?

在生产环境中,数据就是企业的生命线。备份与还原(Backup & Recovery)不仅仅是简单的复制文件,它是数据库管理员(DBA)最后的救命稻草。

1. 核心原因

  • 防止数据丢失:硬件故障(硬盘损坏)、系统崩溃或自然灾害。

  • 人为操作失误 :这是最常见的原因!比如程序员不小心执行了 DROP DATABASE 或忘记加 WHERE 条件的 DELETE 语句。

  • 安全防御:应对勒索病毒攻击或黑客恶意篡改数据。

  • 版本回滚:在进行数据库架构升级或重大代码变更后,如果出现 Bug,需要快速回回到正常状态。

  • 合规与审计:许多行业(如金融、医疗)法律规定必须定期保留数据备份。

2、 什么是备份(Backup)

备份是指通过特定的手段,将数据库的逻辑结构(建库、建表语句)和物理数据(记录行、索引文件)导出并另行保存的过程。

  • 它的本质:是数据的"副本"。

  • 衡量指标

    • RPO (Recovery Point Objective):恢复点目标。即发生故障时,你最多能容忍丢失多少分钟的数据。

    • RTO (Recovery Time Objective):恢复时间目标。即发生故障后,你需要多久能把业务重新拉起来。

3、 什么是还原(Recovery / Restore)

还原是指当数据库发生故障或数据被破坏时,利用之前保存的备份副本,将数据库恢复到某个已知正常状态的操作。

  • 还原的逻辑

    1. 物理还原:把备份的文件直接拷贝回数据库目录。

    2. 逻辑还原:把备份的 SQL 语句在数据库里重新"跑一遍"。

    3. 日志追溯:利用二进制日志(Binlog)将全量备份之后产生的新数据"重做(Redo)"一遍,实现数据的无损恢复。

4、备份类型

1. 根据服务器状态分类(热、温、冷)

这是衡量备份对业务影响程度的关键指标。

2. 根据对象分类(物理 vs 逻辑)

这决定了备份的速度和恢复的灵活性。

  • 物理备份 (Physical Backup)

    • 定义 :直接复制数据库的物理文件(如 Data 目录下的 .ibd、索引文件、日志文件等)。

    • 优点:恢复极快,适合大数据量(TB 级)。

    • 缺点:文件通常比逻辑备份大,且跨平台能力差(如从 Linux 恢复到 Windows 可能会有问题)。

  • 逻辑备份 (Logical Backup)

    • 定义 :将数据库结构和数据导出为文本文件(通常是 .sql.csv 格式)。

    • 优点:可读性强,可以精确到单表、甚至单行恢复,跨平台性极佳。

    • 缺点:备份和恢复速度慢,不适合超大型数据库。

3. 根据数据收集量分类(全量、增量、差异)

这是制定"备份计划"的核心逻辑。

  1. 完全备份 (Full Backup)

    • 备份数据库中全部的数据。它是后续所有备份的基础。
  2. 增量备份 (Incremental Backup)

    • 逻辑 :仅备份自上一次备份(无论是全备还是增量)以来变化的数据。

    • 优点:备份文件小,占用空间少,备份快。

    • 缺点:恢复麻烦,需要按顺序合并所有增量包。

  3. 差异备份 (Differential Backup)

    • 逻辑 :仅备份自上一次完全备份以来变化的数据。

    • 优点:恢复比增量备份简单,只需要全备+最后一次差异备。

    • 缺点:随着时间推移,差异备份的文件会越来越大。

5、常用备份工具

1. mysqldump(官方自带 · 逻辑备份)

这是每一位开发者都必须掌握的"国民级"工具。

  • 类型:逻辑备份(导出 SQL 语句)。

  • 优点

    • 无需安装:MySQL 客户端自带。

    • 兼容性强 :生成的 .sql 文件可以方便地在不同版本的 MySQL 甚至不同数据库间迁移。

    • 灵活:支持备份单表、单库或全库。

  • 缺点

    • 速度慢:大数据量(如超过 50G)时,导出和还原效率较低。
  • 适用场景:小型数据库、开发环境测试、单表数据迁移。

2. mydumper(第三方增强 · 高性能逻辑备份)

如果 mysqldump 太慢,它是最佳的替代方案。

  • 类型:逻辑备份。

  • 优点

    • 多线程并发:备份和还原速度远快于 mysqldump。

    • 文件切分:将表自动切分成多个小文件,方便管理。

    • 一致性好:通过更精确的锁机制控制。

  • 缺点

    • 额外安装:需要单独下载安装包(但在 RHEL 9 中安装很简便)。
  • 适用场景:中型数据库、对备份时长有要求的生产环境。

3. Percona XtraBackup(物理备份专业户)

大厂、大数据的首选方案。

  • 类型:物理备份(拷贝二进制数据文件)。

  • 优点

    • 极速:几乎是磁盘 IO 的上限,适合 TB 级数据。

    • 热备:备份时对业务几乎无影响(针对 InnoDB 引擎)。

  • 缺点

    • 复杂度高:安装步骤多,对新手不友好。

    • 不可跨平台:备份的文件通常只能还原到相同或兼容版本的 Linux 环境下。

  • 适用场景:大型企业级生产环境、核心数据库的全量备份。

4. 小结

工具名称----->安装难度----->备份类型----->速度----->压缩/远程支持----->备份即时点

mysqldump----->系统自带----->逻辑----->慢----->支持----->不支持

mydumper----->简单----->逻辑----->快----->支持----->支持

xtrabackup----->较难----->物理----->较快----->不直接支持----->支持

二、实战

1、数据准备

1. 开启 Binlog (关键)

在 RHEL 9.7 中,MySQL 8.0 默认通常是开启的,但务必先确认:

sql 复制代码
-- 登录 MySQL 后执行
show variables like 'log_bin';

检查点 :确保输出结果为 ON 。如果是 OFF,请修改 /etc/my.cnf 并重启服务。

2. 准备库和表

我们将创建一个 school 数据库,并建立学生表 student 和成绩表 score

第一步:创建库和表

sql 复制代码
-- 1. 创建数据库
CREATE DATABASE school DEFAULT CHARSET utf8mb4;
USE school;

-- 2. 创建学生表 student
CREATE TABLE student (
    id INT(10) NOT NULL UNIQUE PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    sex VARCHAR(4),
    birth YEAR,
    department VARCHAR(20),
    address VARCHAR(50)
); 

-- 3. 创建成绩表 score
CREATE TABLE score (
    id INT(10) NOT NULL UNIQUE PRIMARY KEY AUTO_INCREMENT,
    stu_id INT(10) NOT NULL,
    c_name VARCHAR(20),
    grade INT(10)
); 

第二步:插入首批业务数据(作为"全量"基础)

sql 复制代码
-- 插入学生记录
INSERT INTO student VALUES(901,'张老大', '男',1985,'计算机系', '北京市海淀区');
INSERT INTO student VALUES(902,'张老二', '男',1986,'中文系', '北京市昌平区');
INSERT INTO student VALUES(903,'张三', '女',1990,'中文系', '湖南省永州市');
INSERT INTO student VALUES(904,'李四', '男',1990,'英语系', '辽宁省阜新市');
INSERT INTO student VALUES(905,'王五', '女',1991,'英语系', '福建省厦门市');
INSERT INTO student VALUES(906,'王六', '男',1988,'计算机系', '湖南省衡阳市');

-- 插入成绩记录
INSERT INTO score VALUES(NULL,901, '计算机',98);
INSERT INTO score VALUES(NULL,901, '英语', 80);
INSERT INTO score VALUES(NULL,902, '计算机',65);
INSERT INTO score VALUES(NULL,902, '中文',88);
INSERT INTO score VALUES(NULL,903, '中文',95);
INSERT INTO score VALUES(NULL,904, '计算机',70);
INSERT INTO score VALUES(NULL,904, '英语',92);
INSERT INTO score VALUES(NULL,905, '英语',94); 

第三步:检查数据是否插入

sql 复制代码
SELECT * FROM student;

2、使用 mysqldump 进行完全备份

1. 备份前的环境确认

在执行备份前,我们再次确认数据状态,确保 school 库中有我们刚刚插入的 6 条学生数据和 8 条成绩数据 。

2. 执行完全备份命令

打开终端(Terminal),创建一个备份目录并执行 mysqldump。根据文档建议,备份单个数据库时使用 -B 参数可以带上建库语句 。

powershell 复制代码
# 1. 创建并进入备份目录
mkdir -p /mysqlbak
cd /mysqlbak

# 2. 执行备份
# --opt: 启用快速备份、优化表结构等多种选项
# -B: 指定数据库,备份文件中会包含 CREATE DATABASE 语句
mysqldump --opt -B school -u root -p > school.sql

输入密码后,/mysqlbak 目录下会生成 school.sql 文件。

3. 验证备份文件内容

为了确保备份成功,我们可以查看一下 SQL 文件的头部和关键部分:

powershell 复制代码
# 查看备份文件的前几行
head -n 25 school.sql

4. 模拟数据增长(为增量备份做准备)

完全备份完成后,业务仍在运行。我们模拟向 student 表中再次插入 2 条新数据。

sql 复制代码
USE school;
INSERT INTO student VALUES (907, 'xumubin', '男', 1995, '中文专业', '上海市'),
                           (908, 'wangzhao', '男', 2003, '导弹专业', '西安市');

注意:这两条数据(ID 907 和 908)并没有在刚才的 school.sql 文件中,它们只存在于当前的数据库和 Binlog(二进制日志) 中 。

5. 模拟灾难:误删数据库

现在,我们模拟最核心的实验环节------删库。

sql 复制代码
-- 彻底删除数据库
DROP DATABASE school;

此时,如果你执行 show databases;,会发现 school 库已经消失了。

6. 立即刷新日志

删库后,为了保护现场,我们需要立即执行刷新日志的操作,产生一个新的 Binlog 文件,防止后续操作干扰恢复过程。

sql 复制代码
FLUSH LOGS;

7. 解析 Binlog 与全量+增量恢复

1、恢复的第一步:还原完全备份

在处理增量数据之前,必须先把数据库恢复到"全备"时的状态。

powershell 复制代码
# 方式 1:在 Linux 命令行执行
mysql -u root -p < /mysqlbak/school.sql

# 方式 2:进入 MySQL 命令行执行
# mysql> source /mysqlbak/school.sql

验证 :执行 SELECT * FROM school.student;,你会发现数据回来了,但只有 6 条 。最后插入的 xumubinwangzhao 还没回来。

2、核心操作:定位并提取增量数据

我们需要从二进制日志(Binlog)中把那 2 条 INSERT 语句"抠"出来。

A. 找到正确的 Binlog 文件

在 MySQL 中执行:

sql 复制代码
SHOW BINARY LOGS;

会看到多个文件(如 binlog.000001, binlog.000002)。通常发生误操作前的数据记录在刷新日志前 的那个文件里(假设是 binlog.000001)。

B. 查看并解析日志内容

由于 MySQL 8.0/5.7 的日志可能是加密或以 Base64 存储的,直接查看会乱码。

powershell 复制代码
# 使用 mysqlbinlog 工具查看内容
# -vv 参数可以显示伪 SQL 语句,方便我们定位
mysqlbinlog --base64-output=DECODE-ROWS -vv /var/lib/mysql/binlog.000002

发现了导弹专业,说明,增量备份的时候,操作保存到了002日志中,接下来进行还原即可。

3、执行增量恢复

基于位置点恢复 (Start-Position)

如何精准定位 Binlog 位点?

  1. 搜寻起点 :通过 mysqlbinlog -vv 找到全备后的第一条 INSERT 语句,记录其上方的 # at [数字]

  2. 搜寻终点 :通过 /DROP DATABASE 搜索误操作语句,记录该语句起始 位置的 # at [数字]

  3. 执行提取mysqlbinlog --start-position=起点 --stop-position=终点 binlog.xxxxx > recovery.sql

通过 mysqlbinlog 工具查看 binlog.000002,寻找丢失数据的坐标。

  • Start Position : 5717 (对应 INSERT 事务的 BEGIN)

  • Stop Position : 5908 (对应 DROP DATABASE 的起点)

避坑指南 :在 MySQL 8.0 中,如果直接提取,还原时会因为 GTID 幂等性 导致数据无法写入(系统认为这些事务已经跑过了)。

powershell 复制代码
# 提取指定位置段的日志
# 必须添加 --skip-gtids 参数
mysqlbinlog --start-position=5717 --stop-position=5908 \
            --skip-gtids \
            /var/lib/mysql/binlog.000002 > /mysqlbak/increment.sql
sql 复制代码
# 执行还原
-- 进入 MySQL 命令行执行
USE school;
SET sql_log_bin=0; -- 临时关闭 binlog 记录,提高效率
SOURCE /mysqlbak/increment.sql;
SET sql_log_bin=1;

或者
-- 也可以直接不登录,直接使用命令执行
mysql -uroot -p school < /mysqlbak/increment.sql
4、进入数据库检查:
powershell 复制代码
SELECT * FROM school.student;

3、 mydumper备份

1. 为什么选择 mydumper?

在笔记中,首先要写出它的优势,这能体现你的选型思考:

  • 多线程并发 :备份和还原速度远超 mysqldump

  • 文件切分 :它会将表自动拆分成多个小文件(每个表一个 .sql,甚至每个 chunk 一个文件),方便管理和部分恢复。

  • 一致性控制 :通过 FTWRL(Flush Tables With Read Lock)更好地控制备份瞬间的一致性。

2. 安装 mydumper

sql 复制代码
wget https://github.com/mydumper/mydumper/releases/download/v0.21.3-1/mydumper-0.21.3-1.el9.x86_64.rpm
sql 复制代码
dnf install mydumper-0.21.3-1.el9.x86_64.rpm -y

验证是否安装成功

powershell 复制代码
[root@localhost ~]# mydumper --version
mydumper v0.21.3-1, built against MySQL 8.4.8 with SSL support

3、实战演练:多线程备份 school

powershell 复制代码
# 创建备份存放目录
mkdir -p /mysqlbak/mydumper_out

# 执行多线程备份
# -u 用户, -p 密码, -B 数据库, -t 线程数(建议设为 CPU 核心数), -o 输出目录
mydumper -u root -p 123456 -B school -t 4 -o /mysqlbak/mydumper_out/

4、观察备份结果

执行完后,进入 /mysqlbak/mydumper_out/ 目录

powershell 复制代码
cd /mysqlbak/mydumper_out/
powershell 复制代码
ll

文件说明:

  • metadata:记录了备份开始和结束的时间,以及最重要的 Binlog 位点(Pos)。

  • school-schema-create.sql:建库语句。

  • school.student-schema.sqlstudent 表结构。

  • school.student.00000.sqlstudent 表的数据内容。

  • school.score-schema.sqlscore 表结构。

5、模拟损坏与 myloader 还原

mydumper 对应的还原工具是 myloader

再次删库DROP DATABASE school;

执行还原

powershell 复制代码
# -u 用户, -p 密码, -B 目标库, -d 备份文件目录
myloader -u root -p 123456 -B school -d /mysqlbak/mydumper_out/

验证是否还原

powershell 复制代码
mysql -uroot -p'123456'

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| school             |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> select * from school.student;
+-----+-----------+------+-------+--------------+--------------------+
| id  | name      | sex  | birth | department   | address            |
+-----+-----------+------+-------+--------------+--------------------+
| 901 | 张老大    | 男   |  1985 | 计算机系     | 北京市海淀区       |
| 902 | 张老二    | 男   |  1986 | 中文系       | 北京市昌平区       |
| 903 | 张三      | 女   |  1990 | 中文系       | 湖南省永州市       |
| 904 | 李四      | 男   |  1990 | 英语系       | 辽宁省阜新市       |
| 905 | 王五      | 女   |  1991 | 英语系       | 福建省厦门市       |
| 906 | 王六      | 男   |  1988 | 计算机系     | 湖南省衡阳市       |
| 907 | xumubin   | 男   |  1995 | 中文专业     | 上海市             |
| 908 | wangzhao  | 男   |  2003 | 导弹专业     | 西安市             |
+-----+-----------+------+-------+--------------+--------------------+
8 rows in set (0.00 sec)

4、LVM 快照实现"瞬时"物理备份

1. LVM 快照备份的原理

它就像给数据库拍了一张'全身照'。拍照的一瞬间,数据库会短暂锁表,快照创建完(通常只需几秒)立刻解锁。随后你可以慢慢拷贝这张照片里的数据,而数据库已经恢复了正常的读写。

2. 实战环境检查

首先,我们要确认你的分区支持 LVM:

powershell 复制代码
# 查看逻辑卷信息
lvs
# 查看卷组剩余空间(快照需要占用卷组剩余空间)
vgs

注意:如果你的 MySQL 没装在 LVM 分区上,这个实验需要在虚拟机里添加一块新硬盘并配置 LVM 挂载点

这种是不行的,需要重新添加一块磁盘。

添加并配置新磁盘(模拟生产环境)
  • 在虚拟机设置里添加一块新硬盘(比如 20G)。

  • 创建物理卷 (PV) : pvcreate /dev/sdb

  • 创建卷组 (VG) : vgcreate vg_mysql /dev/sdb

  • 创建逻辑卷 (LV) : lvcreate -L 10G -n lv_data vg_mysql

  • 格式化并挂载 : 格式化为 xfsext4,然后把 MySQL 的数据迁移到这个新分区上。

注意:在实际生产中,MySQL 数据必须存放在独立的 LVM 逻辑卷上。如果数据在根分区且没有剩余卷组空间,是无法创建快照的。

第一步:构建 LVM 逻辑卷 (物理机/虚拟机操作)

我们要把这块新硬盘变成 LVM 结构。

先使用lbslk查看新加的磁盘的名称

powershell 复制代码
[root@localhost ~]# lsblk
NAME          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda             8:0    0   20G  0 disk
sr0            11:0    1 12.7G  0 rom  /mnt
nvme0n1       259:0    0  100G  0 disk
├─nvme0n1p1   259:1    0  600M  0 part /boot/efi
├─nvme0n1p2   259:2    0    1G  0 part /boot
└─nvme0n1p3   259:3    0 98.4G  0 part
  ├─rhel-root 253:0    0 63.5G  0 lvm  /
  ├─rhel-swap 253:1    0  3.9G  0 lvm
  └─rhel-home 253:2    0   31G  0 lvm  /home

检查点 :看 sda 是否存在,且它的 TYPE 应该是 disk,下方不应该有任何 part(分区)。

powershell 复制代码
# 1. 创建物理卷 (PV)
pvcreate /dev/sda

# 2. 创建卷组 (VG),命名为 vg_mysql
vgcreate vg_mysql /dev/sda

# 3. 创建逻辑卷 (LV),分配 10G 空间(留 10G 给快照用)
lvcreate -L 10G -n lv_mysql_data vg_mysql

# 4. 格式化为ext4文件系统
mkfs.ext4 /dev/vg_mysql/lv_mysql_data
第二步:数据大迁移(将 MySQL 搬入 LVM)

这是模拟生产环境中最真实的一步:如何不停机/短暂停机迁移数据。

powershell 复制代码
# 1. 停止 MySQL 服务(确保数据一致性)
systemctl stop mysqld

# 2.备份所有的数据文件到指定的地方
tar -zcf /tmp/mysql.tar.gz /var/lib/mysql/*

# 3.查看是否打包
tar -tf /tmp/mysql.tar.gz

# 4.挂载逻辑卷到当前 MySQL 的数据目录里
 mount /dev/vg_mysql/lv_mysql_data /var/lib/mysql
 
 # 5.查看是否还有文件,这是查看是查看不到的,系统已经把文件隐藏了
 ls /var/lib/mysql
 
 # 6.解压/tmp/mysql.tar.gz到/
 tar -xzf /tmp/mysql.tar.gz -C /
 
 # 7.查看/var/lib/mysql下面是否有数据
 ls /var/lib/mysql

现在发现有数据了

powershell 复制代码
[root@localhost ~]# ls /var/lib/mysql
 auto.cnf        binlog.000003   binlog.index   client-cert.pem     '#ib_16384_1.dblwr'  '#innodb_redo'   mysql                   performance_schema   school            sys
 binlog.000001   binlog.000004   ca-key.pem     client-key.pem       ib_buffer_pool      '#innodb_temp'   mysql.ibd               private_key.pem      server-cert.pem   undo_001
 binlog.000002   binlog.000005   ca.pem        '#ib_16384_0.dblwr'   ibdata1              lost+found      mysql_upgrade_history   public_key.pem       server-key.pem    undo_002
[root@localhost ~]#

更改目录的权限

powershell 复制代码
chown -R mysql: /var/lib/mysql

启动MySQL

powershell 复制代码
systemctl start mysqld

3. LVM备份

安装rsync工具

powershell 复制代码
dnf install rsync -y

编写备份脚本vim bak_mysql.sh

powershell 复制代码
#!/bin/bash

# 1. 定义备份目录(确保 /bak 目录存在)
bak_dir=/bak/$(date +%F)
[ -d ${bak_dir} ] || mkdir -p ${bak_dir}

# 2. 执行快照(核心路径修改点)
# 注意:这里 /dev/vg_mysql/lv_mysql_data 是你刚刚创建的源卷
# -n lv_mysql_s 是快照的名字
echo "flush tables with read lock; system lvcreate -n lv_mysql_s -L 500M -s /dev/vg_mysql/lv_mysql_data; unlock tables;" | mysql -uroot -p'123456'

# 3. 挂载快照卷
[ -d /mnt/mysql/ ] || mkdir -p /media/mysql
# 挂载路径也要对应卷组名和快照名
mount /dev/vg_mysql/lv_mysql_s /media/mysql

# 4. 同步数据
rsync -az /media/mysql/ ${bak_dir}

# 5. 清理现场
if [ $? -eq 0 ]; then
    umount /media/mysql/ && lvremove -f /dev/vg_mysql/lv_mysql_s &>/dev/null
    echo "恭喜!LVM 物理备份已成功完成,存放于 ${bak_dir}"
fi

注意:这里是挂载在/media/mysql/,不能使用/mnt/mysql,因为使用这个目录的话,系统会报错,因为/mnt目录只有只读权限,不能在/mnt下面创建目录,因此这里使用的挂载目录是/media

检查脚本是否有语法错误

powershell 复制代码
sh -n bak_mysql.sh

没有语法错误就直接运行脚本

powershell 复制代码
bash bak_mysql.sh

备份完成后就可以查看备份目录中是否有文件。

powershell 复制代码
ls /bak/2026-04-04/
# /bak/2026-04-04/ 这个日期是你自己执行lvs备份数据库的这个日期,也就是你执行这个命令当天的日期

4. 还原数据

powershell 复制代码
# 1. 停止当前运行的数据库服务(防止文件冲突)
systemctl stop mysqld

# 2. 修改 MySQL 核心配置文件
vim /etc/my.cnf
# 找到 [mysqld] 模块下的 datadir 参数
# 将 datadir=/var/lib/mysql 修改为你的备份目录路径
# 例如:datadir=/bak/2026-04-04/

# 3. 关键步骤:修正目录权限
# 物理备份的文件必须属于 mysql 用户,否则数据库无法启动
chown -R mysql:mysql /bak/2026-04-04

# 4. 启动数据库
systemctl start mysqld

# 5. 登录验证
mysql -uroot -p'你的密码'
# 此时执行 show databases; 你会发现数据已经完全恢复到了备份时的状态

至此,LVM备份成功。

5、xtrabackup备份

1. 工具简介 (Why XtraBackup?)

XtraBackup 是由 Percona 公司开发的开源物理备份工具。

  • 备份类型:物理备份(直接复制数据文件)。

  • 备份速度:较快 。

  • 核心优势 :支持热备份,即在备份过程中数据库的读、写操作均不受影响。

2. 环境准备与安装 (针对 RHEL 9.x)

对于 MySQL 8.0 及以上版本,建议使用 XtraBackup 8.0/8.4 系列。

powershell 复制代码
# 1. 下载并安装 Percona 仓库及工具
dnf install https://repo.percona.com/yum/percona-release-latest.noarch.rpm -y
percona-release enable-only tools release
dnf install percona-xtrabackup-80 -y

配置备份专属用户(推荐):

sql 复制代码
CREATE USER 'bkuser'@'%' IDENTIFIED with caching_sha2_password BY 'Back@123';
GRANT BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'bkuser'@'%';
GRANT SELECT ON performance_schema.log_status TO 'bkuser'@'%';
GRANT SELECT ON performance_schema.keyring_component_status TO 'bkuser'@'%';
GRANT SELECT ON performance_schema.replication_group_members TO 'bkuser'@'%';
FLUSH PRIVILEGES;

3. 全量备份 (Full Backup) 实战

执行物理备份时,数据将直接拷贝到指定目录 。

powershell 复制代码
# 创建备份存放目录(建议存放在您的新磁盘挂载点下)
mkdir -p /backup/xtra/{base,incr1,incr2}

# 执行全量备份
xtrabackup --defaults-file=/etc/my.cnf --backup \
  --target-dir=/backup/xtra/base \
  --user=bkuser --password='Back@123' 

4. 增量备份 (Incremental Backup) 实战

增量备份仅备份自上次备份以来变化的数据。插入数据

sql 复制代码
INSERT INTO school.student (id, name, sex, birth, department, address)
VALUES (8, 'alice', '女', 2001, '计算机科学', '北京市海淀区');

第一次增量备份(基于全备):

powershell 复制代码
xtrabackup --defaults-file=/etc/my.cnf --backup --target-dir=/backup/xtra/incr1/ --incremental-basedir=/backup/xtra/base --user=bkuser --password='Back@123'

--defaults-file=/etc/my.cnf

含义:指定 MySQL 的配置文件路径。

作用:让 xtrabackup 读取该文件中的配置(如数据目录位置等),确保备份环境与数据库运行环境一致。

--backup

含义:启动备份操作。

作用:这是告诉 xtrabackup 开始执行备份任务的核心参数。

--target-dir=/backup/xtra/incr1/

含义:指定备份的目的目录。

作用:备份生成的文件将存储在这个路径下。在这个例子中,它是一个增量备份的存储目录。

--incremental-basedir=/backup/xtra/base

含义:增量备份的基础目录。

作用:指定上一次全量备份(或上一次增量备份)所在的目录。xtrabackup 会对比这个目录中的数据,只备份发生变化的数据,从而实现增量备份。

--user=bkuser

含义:指定连接数据库的用户名。

作用:这里使用的是之前创建的 bkuser 用户。

--password='Back@123'

含义:指定连接数据库的密码。

作用:这是 bkuser 用户的密码,用于验证身份。

再次插入一条数据:

powershell 复制代码
INSERT INTO school.student (id, name, sex, department)
VALUES (9, 'bob', '男', '软件工程');

第二次增量备份(基于上一次增量):

powershell 复制代码
/usr/bin/xtrabackup --defaults-file=/etc/my.cnf --backup --target-dir=/backup/xtra/incr2/ --incremental-basedir=/backup/xtra/incr1/ --user=bkuser --password='Back@123'

5. 数据还原

powershell 复制代码
-- 模拟破坏数据 (注意:此操作会删除数据库,请在测试环境执行)
mysql> drop database school;

-- 准备完全备份 (注意:必须加上 --apply-log-only,除了最后一次)
xtrabackup --prepare --apply-log-only --target-dir=/backup/xtra/base/

-- 应用第一次增量备份到完全备份 (注意:--incremental-dir 指向 incr1)
xtrabackup --prepare --apply-log-only --target-dir=/backup/xtra/base/ --incremental-dir=/backup/xtra/incr1/

-- 应用第二次增量备份到完全备份 (注意:--incremental-dir 指向 incr2)
xtrabackup --prepare --apply-log-only --target-dir=/backup/xtra/base/ --incremental-dir=/backup/xtra/incr2/

-- 最后执行恢复操作
# 1. 最终 Prepare (注意:最后一次合并不需要 --apply-log-only,确保数据回滚段清理)
xtrabackup --prepare --target-dir=/backup/xtra/base/

# 2. 停止 MySQL 服务
systemctl stop mysqld

# 3. 清空原数据目录 (注意:此操作极其危险,会删除所有现有数据,请确保已备份)
rm -rf /var/lib/mysql/*

# 4. 将备份文件拷贝回原数据目录
xtrabackup --copy-back --target-dir=/backup/xtra/base/

# 5. 修改文件属主 (注意:必须改回 mysql 用户,否则数据库无法启动)
chown -R mysql:mysql /var/lib/mysql

# 6. 启动 MySQL 服务
systemctl start mysqld

3、基于GTID的备份实战

一、 什么是 GTID?

GTID (Global Transaction Identifier) 即 全局事务标识符

在传统的备份还原中,我们依赖 binlog 文件名和 position(位置点)来定位。但这种方式在多库环境或发生故障切换时非常混乱。 GTID 的核心意义 :为每一个提交的事务分配一个全网唯一的编号。无论事务被传送到哪台服务器,它的编号永远不变。

1. GTID 的格式

GTID 由两部分组成:

GTID = source_id : transaction_id

  • source_id :源服务器的唯一标识(即 server_uuid)。

  • transaction_id:在该服务器上提交的事务序列号,从 1 开始顺序排列。

  • 示例6181523d-bc2e-11ea-a78b-000c29221146:1-10(代表该 UUID 服务器上的前 10 个事务)。

2. 存储与版本支持

  • 版本支持 :MySQL 5.6 引入,5.7 完善,8.0 成为标配

  • 存储位置

    • 内存 :实时记录在全局变量 @@global.gtid_executed 中。

    • 磁盘 :持久化在 binlog 日志以及系统表 mysql.gtid_executed 中。


二、 如何开启 GTID

在 RHEL 9.7 (MySQL 8.4.0) 中,开启 GTID 需要修改配置文件。

实战步骤:

  1. 编辑配置文件
plain 复制代码
vim /etc/my.cnf
  • [mysqld] 模块下添加以下四行
powershell 复制代码
gtid_mode=ON                       # 开启 GTID 模式
enforce-gtid-consistency=ON        # 强制 GTID 一致性(如不支持 create table ... select)
log-bin=mysql-bin                  # 必须开启 Binlog
log-slave-updates=ON               # 从库也记录事务日志(高可用必备)
  • 重启服务
plain 复制代码
systemctl restart mysqld
  • 验证状态
sql 复制代码
mysql> show variables like '%gtid%';
-- 看到 gtid_mode 为 ON 即表示成功!

三、 DDL 和 DML 如何查看 GTID?

开启 GTID 后,每一个操作都会带上身份勋章。

1. 查看当前已经执行了哪些 GTID

sql 复制代码
show binary log status;

2. 查看具体 SQL 对应的 GTID

使用 mysqlbinlog 工具查看日志:

powershell 复制代码
# 查看最新的 binlog 文件
mysqlbinlog -vv /var/lib/mysql/mysql-bin.000001
  • DDL (如 Create) :你会看到在 CREATE TABLE 语句之前,有一行 SET @@SESSION.GTID_NEXT='...:N'

  • DML (如 Insert) :在事务开始的 BEGIN 之前,同样会打上 GTID 标记。

四、 保姆级实战:如何利用 GTID 实现备份恢复?

这是最关键的实战环节。假设我们不小心删掉了 school 库中的几条数据,需要通过 Binlog 恢复到指定状态。

场景模拟:

  1. 正常操作:插入了 5 条数据(产生 GTID 1-5)。

  2. 误操作:删除了数据(产生 GTID 6)。

  3. 需求:恢复到 GTID 1-5 的状态。

先创建数据

sql 复制代码
-- 1. 创建名为 gtid 的实验数据库
mysql> create database gtid;
Query OK, 1 row affected (0.01 sec)

-- 2. 切换到该数据库
mysql> use gtid;
Database changed

-- 3. 创建一张简单的实验表 t1,包含一个整数列 id
-- 此时会产生一个 DDL 事务,分配一个全新的 GTID
mysql> create table t1(id int);
Query OK, 0 rows affected (0.00 sec)

-- 4. 插入数据(语法错误示例)
-- 虽然执行失败,但由于语法解析阶段就报错了,通常不会消耗 GTID 序列号
mysql> insert into t1(12);
ERROR 1064 (42000): ...(语法错误)

-- 5. 正确插入第一条数据:12
-- 产生一个 DML 事务,GTID 序列号增加
mysql> insert into t1 value(12);
Query OK, 1 row affected (0.00 sec)

-- 6. 正确插入第二条数据:13
-- 产生一个 DML 事务,GTID 序列号继续增加
mysql> insert into t1 value(13);
Query OK, 1 row affected (0.00 sec)

-- 7. 正确插入第三条数据:23
-- 产生一个 DML 事务,GTID 序列号继续增加
mysql> insert into t1 value(23);
Query OK, 1 row affected (0.00 sec)

-- 8. 查看当前的二进制日志状态(核心观察点)
mysql> show binary log status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000001 |     2560 |              |                  | 74587089-3025-11f1-863c-000c2905496c:1-10 |
+------------------+----------+--------------+------------------+-------------------------------------------+
/* 【结果分析】:
  - File: 当前正在写入的日志文件是 mysql-bin.000001。
  - Position: 文件偏移量为 2560。
  - Executed_Gtid_Set: 74587089-3025-11f1-863c-000c2905496c:1-10
    表示该 UUID 对应的服务器上已经执行了从 1 到 10 号的所有事务。
    这就意味着你刚才的建库、建表、插入等操作已经占用了这 10 个号中的一部分。
*/

-- 然后删除这个数据库,后续进行备份
drop database gtid;

步骤 1:寻找目标 GTID 范围

登录数据库查看:

powershell 复制代码
mysql> show binlog events in 'mysql-bin.000001';
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
| Log_name         | Pos  | Event_type     | Server_id | End_log_pos | Info                                                               |
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
| mysql-bin.000001 |    4 | Format_desc    |         1 |         127 | Server ver: 8.4.8, Binlog ver: 4                                   |
| mysql-bin.000001 |  127 | Previous_gtids |         1 |         158 |                                                                    |
| mysql-bin.000001 |  158 | Gtid           |         1 |         235 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:1'  |
| mysql-bin.000001 |  235 | Query          |         1 |         350 | use `school`; create table y1(id int) /* xid=16 */                 |
| mysql-bin.000001 |  350 | Gtid           |         1 |         429 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:2'  |
| mysql-bin.000001 |  429 | Query          |         1 |         506 | BEGIN                                                              |
| mysql-bin.000001 |  506 | Table_map      |         1 |         556 | table_id: 94 (school.y1)                                           |
| mysql-bin.000001 |  556 | Write_rows     |         1 |         596 | table_id: 94 flags: STMT_END_F                                     |
| mysql-bin.000001 |  596 | Xid            |         1 |         627 | COMMIT /* xid=17 */                                                |
| mysql-bin.000001 |  627 | Gtid           |         1 |         706 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:3'  |
| mysql-bin.000001 |  706 | Query          |         1 |         783 | BEGIN                                                              |
| mysql-bin.000001 |  783 | Table_map      |         1 |         833 | table_id: 94 (school.y1)                                           |
| mysql-bin.000001 |  833 | Write_rows     |         1 |         873 | table_id: 94 flags: STMT_END_F                                     |
| mysql-bin.000001 |  873 | Xid            |         1 |         904 | COMMIT /* xid=24 */                                                |
| mysql-bin.000001 |  904 | Gtid           |         1 |         983 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:4'  |
| mysql-bin.000001 |  983 | Query          |         1 |        1060 | BEGIN                                                              |
| mysql-bin.000001 | 1060 | Table_map      |         1 |        1110 | table_id: 94 (school.y1)                                           |
| mysql-bin.000001 | 1110 | Write_rows     |         1 |        1150 | table_id: 94 flags: STMT_END_F                                     |
| mysql-bin.000001 | 1150 | Xid            |         1 |        1181 | COMMIT /* xid=25 */                                                |
| mysql-bin.000001 | 1181 | Gtid           |         1 |        1258 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:5'  |
| mysql-bin.000001 | 1258 | Query          |         1 |        1368 | drop database school /* xid=28 */                                  |
| mysql-bin.000001 | 1368 | Gtid           |         1 |        1445 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:6'  |
| mysql-bin.000001 | 1445 | Query          |         1 |        1553 | create database gtid /* xid=31 */                                  |
| mysql-bin.000001 | 1553 | Gtid           |         1 |        1630 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:7'  |
| mysql-bin.000001 | 1630 | Query          |         1 |        1741 | use `gtid`; create table t1(id int) /* xid=36 */                   |
| mysql-bin.000001 | 1741 | Gtid           |         1 |        1820 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:8'  |
| mysql-bin.000001 | 1820 | Query          |         1 |        1895 | BEGIN                                                              |
| mysql-bin.000001 | 1895 | Table_map      |         1 |        1943 | table_id: 98 (gtid.t1)                                             |
| mysql-bin.000001 | 1943 | Write_rows     |         1 |        1983 | table_id: 98 flags: STMT_END_F                                     |
| mysql-bin.000001 | 1983 | Xid            |         1 |        2014 | COMMIT /* xid=38 */                                                |
| mysql-bin.000001 | 2014 | Gtid           |         1 |        2093 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:9'  |
| mysql-bin.000001 | 2093 | Query          |         1 |        2168 | BEGIN                                                              |
| mysql-bin.000001 | 2168 | Table_map      |         1 |        2216 | table_id: 98 (gtid.t1)                                             |
| mysql-bin.000001 | 2216 | Write_rows     |         1 |        2256 | table_id: 98 flags: STMT_END_F                                     |
| mysql-bin.000001 | 2256 | Xid            |         1 |        2287 | COMMIT /* xid=39 */                                                |
| mysql-bin.000001 | 2287 | Gtid           |         1 |        2366 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:10' |
| mysql-bin.000001 | 2366 | Query          |         1 |        2441 | BEGIN                                                              |
| mysql-bin.000001 | 2441 | Table_map      |         1 |        2489 | table_id: 98 (gtid.t1)                                             |
| mysql-bin.000001 | 2489 | Write_rows     |         1 |        2529 | table_id: 98 flags: STMT_END_F                                     |
| mysql-bin.000001 | 2529 | Xid            |         1 |        2560 | COMMIT /* xid=40 */                                                |
| mysql-bin.000001 | 2560 | Gtid           |         1 |        2637 | SET @@SESSION.GTID_NEXT= '74587089-3025-11f1-863c-000c2905496c:11' |
| mysql-bin.000001 | 2637 | Query          |         1 |        2741 | drop database gtid /* xid=42 */                                    |
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+

由于都是在同一个日志文件中操作的,因此列出该日志文件中的开始的事务id和结束的事务id

powershell 复制代码
mysql-bin.000001                   74587089-3025-11f1-863c-000c2905496c:6
mysql-bin.000001                   74587089-3025-11f1-863c-000c2905496c:11
# 但是呢这里的事务id=11不能取,因为这是删除数据库的事务id,因此结束id要取的是10

完整的事务id起始和结束如下:

powershell 复制代码
mysql-bin.000001                   74587089-3025-11f1-863c-000c2905496c:6
mysql-bin.000001                   74587089-3025-11f1-863c-000c2905496c:10

步骤 2:利用 mysqlbinlog 导出数据(关键点!)

注意: 必须使用 --skip-gtids 参数,否则恢复时会因为幂等性被系统忽略。

powershell 复制代码
# 截取 GTID 6 到 10 的数据导出到 SQL 文件
# 注意注意:一定要进入到/var/lib/mysql目录文件中去执行下面的命令
[root@localhost ~]# cd /var/lib/mysql
[root@localhost mysql]# mysqlbinlog --skip-gtids --include-gtids='74587089-3025-11f1-863c-000c2905496c:6-10' mysql-bin.000001 > /root/gtid1.sql
[root@localhost mysql]#

# 或者
mysqlbinlog --skip-gtids --include-gtids='89-3025-11f1-863c-000c2905496c:6-10'  /var/lib/mysql/mysql-bin.000001 > /root/gtid1.sql

步骤 3:导入数据并控制 Binlog 生成

为了保证恢复过程不产生垃圾日志,也为了避免冲突:

sql 复制代码
-- 1. 进入 MySQL
mysql -uroot -p

-- 2. 临时关闭当前会话的 Binlog 记录
set sql_log_bin=0;

-- 3. 导入 SQL
source /root/gtid1.sql;

-- 4. 重新开启 Binlog 记录
set sql_log_bin=1;

五、 GTID 模式下的备份恢复总结

  1. 优势:不再需要翻找 Position 字节偏移量,直接通过事务编号(1-5, 7-10)截取。

  2. 核心坑点幂等性

    • 如果你不加 --skip-gtids:MySQL 导入时会检查:"咦,GTID 1-5 我已经运行过了呀,为了数据安全,我跳过吧。" ------ 结果就是恢复了个寂寞。

    • 如果你加了 --skip-gtids:MySQL 导入时会认为:"哦,这是一些没有身份的新数据,我得赶紧存进去。" ------ 恢复成功。

小结:每次操作数据库(增删改数据库/表/数据)的时候都会产生一个全局事务id。

相关推荐
麦聪聊数据5 小时前
企业数据流通与敏捷API交付实战(五):异构数据跨库联邦与零代码发布
数据库·sql·低代码·restful
Elastic 中国社区官方博客5 小时前
当 TSDS 遇到 ILM:设计不会拒绝延迟数据的时间序列数据流
大数据·运维·数据库·elasticsearch·搜索引擎·logstash
qing222222225 小时前
Linux中修改mysql数据表
linux·运维·mysql
Omics Pro5 小时前
虚拟细胞:开启HIV/AIDS治疗新纪元的关键?
大数据·数据库·人工智能·深度学习·算法·机器学习·计算机视觉
J2虾虾5 小时前
MySQL的基本操作
数据库·mysql
arvin_xiaoting5 小时前
OpenClaw学习总结_III_自动化系统_3:CronJobs详解
数据库·学习·自动化
杨云龙UP5 小时前
Oracle 中 NOMOUNT、MOUNT、OPEN 怎么理解? 在不同场景下如何操作?_20260402
linux·运维·数据库·oracle
jzwugang6 小时前
postgresql链接详解
数据库·postgresql
2601_949815336 小时前
MySQL输入密码后闪退?
数据库·mysql·adb
lifewange6 小时前
Redis的测试要点和测试方法
数据库·redis·缓存