---
url: >-
  /courses/cloud-platform-build-management/07-database-replication-recovery/index.md
---
# 第七次课：数据库复制、备份与恢复

## 进入本课环境

* `[Windows PowerShell] wsl ~`：打开本机默认的 WSL2 Ubuntu；出现 `用户名@主机名:~$` 后再执行标为 `[WSL]` 的命令。
* `[WSL] ssh root@你的ECS公网IP`：从 WSL 连接自己的 ECS；如果登录用户不是 `root`，请换成控制台显示的实际用户，看到远端提示符后再执行标为 `[ECS]` 的命令。

第六次课已经把 Web 和 API 拆成两个服务，但业务真正重要的数据还没有得到保护。本课重点关注两台 MariaDB：`primary` 接收写入，`replica` 持续接收主库的二进制日志。

复制正常时，在主库新增一条课程记录，从库很快就能查到。可是如果有人在主库误删数据库，这条删除操作也会被复制过去。

::: tip 关键提醒
从库提高了在线数据的可用性，却不能代替一份经过恢复验证的独立备份。
:::

::: warning 安全边界
本课只操作 `cloud-course-lab-07` 项目中的 `course_db` 测试库。执行 DROP DATABASE 前，必须同时确认当前 Compose 项目、目标库名和备份校验值；不得连接或删除服务器上的其他数据库。
:::

## 7.1 复制、备份和恢复分别解决什么

**主从复制**（主库把数据变更记录发送给从库，从库按顺序重放这些变更） 解决的是在线高可用问题，比如增加只读副本、分担查询压力，或者为故障切换做准备。它的核心关注点是“持续流动的在线数据”。

**逻辑备份**（把库表结构和数据导出为可重新执行的 SQL） 解决的是时点数据保存问题。适合数据量适中、需要查看内容或者跨环境迁移的场景。它的核心关注点是“某个历史时刻可独立归档的静态材料”。

**恢复验证**（把备份导入隔离目标并核对结构、记录和业务结果） 解决的是“这份备份究竟能不能救命”的问题。备份文件躺在磁盘里、`sha256sum` 对得上，绝不等于数据真的能成功恢复。

三者各司其职，谁也替不了谁：

| 能力 | 主要价值 | 典型限制 |
|---|---|---|
| 主从复制 | 保留在线副本，支持只读查询与故障切换 | 误删库、坏 SQL 会立刻同步到从库 |
| 逻辑备份 | 得到可离线保存的 SQL 文件 | 大库导出导入较慢，需注意敏感数据脱敏 |
| 恢复验证 | 确认结构和数据能完整重建 | 需要隔离演练环境、时间与验收标准 |

## 7.2 先读懂一主一从的 Compose 项目

实训包定义两个 MariaDB 11.4 服务：

```txt
primary --二进制日志--> replica
   |                       |
primary-data           replica-data
```

`primary` 与 `replica` 分别指定不同的 `server-id`。主库开启二进制日志（binlog），从库使用专用账号连接主库同步。出于安全考虑，两项服务都不对外暴露宿主机 3306 端口。

在 **ECS** 复制配置文件并完成检查：

```bash
[ECS] mkdir -p ~/cloud-course/lab-07
[ECS] cd ~/cloud-course/lab-07
[ECS] cp starter/compose.yaml starter/seed.sql .
[ECS] cp starter/.env.example .env
[ECS] chmod 600 .env
```

这里的 `.env` 用来存放本次实验的临时密码，只保存在本地 ECS。请用密码管理器或 `openssl rand -base64 24` 生成强密码，不要直接照抄示例里的占位符。`compose.yaml` 和 `seed.sql` 来自实训包（从智慧职教课程资源区下载 `lab-07-starter.zip` 解压）。

```bash
[ECS] docker compose config --services
```

该命令只读取并解析配置，预期输出 `primary` 和 `replica`。此时数据库还没真正启动，复制线程还没通。

```bash
[ECS] docker compose config --quiet
```

若命令静默返回且退出码为 0，说明 YAML 格式与变量语法正确。如果提示变量缺失，先到 `.env` 中补全；明文密码不要写进 `compose.yaml`。

## 7.3 启动后不能只看容器 Up

启动服务前，先查看系统资源与项目边界：

```bash
[ECS] free -h
```

重点关注 `available` 剩余内存。两个数据库实例同时运行会吃掉一定内存，如果系统剩余资源不足，应先暂停后续部署并留存检查日志。

```bash
[ECS] docker compose ls
```

确认当前操作的项目是 `cloud-course-lab-07`，别把命令敲到了其他线上项目里。

接着启动并观察容器健康状态：

```bash
[ECS] docker compose up -d
[ECS] docker compose ps
```

当 `primary` 与 `replica` 都显示 `healthy` 时，说明数据库进程自身的健康检查已通过。但这还不够，必须深入查看主从复制线程的运行指标：

```bash
[ECS] docker compose exec -T replica sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" -e "SHOW REPLICA STATUS\G"' \
  | grep -E 'Slave_(IO|SQL)_Running|Seconds_Behind_Master'
```

预期 `Slave_IO_Running: Yes`、`Slave_SQL_Running: Yes`。`Seconds_Behind_Master` 是观察项，不应把一次为 0 解释成”以后永远没有延迟”。它通过比较主库写入事件的时间戳和从库当前处理进度来计算——如果主库长时间没有新写入，即使从库之前远远落后，这个值也可能显示 0。所以不能只看这一个数字，必须和业务数据查询互相印证。

{{guided-demo:lesson-07-data-protection-path}}

## 7.4 用“主库写、从库读”验证数据流

先查看 starter 中的 `seed.sql`，确认它只创建 `course_check` 表并写入一条测试记录。然后把它交给主库客户端执行：

```bash
[ECS] docker compose exec -T primary sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" course_db' \
  < seed.sql
```

`seed.sql` 只针对 `course_db.course_check`。其中的 `ON DUPLICATE KEY UPDATE` 使重复执行不会因为主键已存在而中断。

随后在从库查询：

```bash
[ECS] docker compose exec -T replica sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" course_db \
  -e "SELECT id,item FROM course_check ORDER BY id;"'
```

从库出现 `1  primary-write`，才说明这条具体业务数据已经到达副本。`SHOW REPLICA STATUS` 和业务查询是一组互补证据：前者看复制机制，后者看实际数据。

## 7.5 从库不是备份：误删也会传播

本课用一份很小的逻辑备份走通恢复流程。先在主库容器内导出，再把标准输出写到 ECS 的课程备份目录：

```bash
[ECS] mkdir -p backup
[ECS] docker compose exec -T primary sh -lc \
  'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" \
  --single-transaction --routines --events --databases course_db' \
  > backup/course_db.sql
```

`--single-transaction` 适用于本课 InnoDB 表，在导出期间尽量保持一致视图；`--routines` 和 `--events` 明确包含存储过程与事件。SQL 文件可能含业务数据，不能上传到公开位置。

检查文件和校验值：

```bash
[ECS] ls -lh backup/course_db.sql
[ECS] sha256sum backup/course_db.sql | tee backup/course_db.sql.sha256
```

`ls` 用于确认文件大小，`sha256sum` 为后续传输或恢复前比较内容提供依据。校验正确仍不等于恢复成功。

在执行删除前完成三项确认：

```bash
[ECS] docker compose ls
```

确认当前项目名。

```bash
[ECS] docker compose exec -T primary sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" -Nse \
  "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME=\"course_db\";"'
```

输出必须严格为 `course_db`，不能是空值或其他名称。

```bash
[ECS] sha256sum -c backup/course_db.sql.sha256
```

必须返回 `OK`。然后才删除这一个测试库：

```bash
[ECS] docker compose exec -T primary sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" -e "DROP DATABASE course_db;"'
```

稍等后，从库上的 `course_db` 也会消失。这说明复制如实同步了错误操作。

## 7.6 从逻辑备份恢复并完成回归检查

把 SQL 重新导入主库：

```bash
[ECS] docker compose exec -T primary sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD"' \
  < backup/course_db.sql
```

这里使用 `mariadb` 客户端执行备份中的建库、建表和插入语句。导入命令退出码为 0 后，继续核对主库：

```bash
[ECS] docker compose exec -T primary sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" course_db \
  -e "SELECT id,item FROM course_check ORDER BY id;"'
```

主库重新出现记录后，再到从库查询同一张表：

```bash
[ECS] docker compose exec -T replica sh -lc \
  'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" course_db \
  -e "SELECT id,item FROM course_check ORDER BY id;"'
```

主库恢复成功、从库再次收到恢复产生的变更，整个恢复流程才算完整走通。

这组记录把导出、确认项目、检查数据库、核对校验和、删除测试库和导入备份放在同一条时间线上。命令中的 `...` 是脱敏后的省略符，不能直接复制；实训时应使用前文给出的完整命令。

读图时先看命令，再看唯一一行输出。`OK` 只说明当前 `course_db.sql` 与校验文件记录的字节一致；它还没有回答 SQL 能否导入，所以后面仍要做恢复和业务查询。

表头下重新出现 `1  primary-write`，说明备份导入主库后，这一条测试记录再次到达从库。这里只核对了课程表中的一条记录，不能把它扩大解释成生产库的表结构、账号权限和全部数据都已经恢复。

{{chat-lab:lesson-07-database-evidence}}

## 7.7 常见故障怎样分层

**两项容器都 Up，但复制 IO 线程为 No**

先查看从库日志和 `Last_IO_Error`。常见原因是主库服务名、复制账号或网络连接问题。不要先删除数据卷，否则会丢失最有价值的故障现场。

**IO 为 Yes，SQL 为 No**

从库能收到日志，但重放时遇到 SQL 错误。查看 `Last_SQL_Error` 和失败位置，先理解冲突对象，不要盲目跳过事件。

**备份文件存在，但导入失败**

先运行 `head` 查看文件开头、`sha256sum -c` 核对内容，再确认导出和导入使用的 MariaDB 客户端版本。MariaDB 11 的容器中应使用 `mariadb-dump`，不要依赖已经移除的 `mysqldump` 别名。

**恢复后主库有数据，从库暂时没有**

重新查看复制线程和延迟，等待短暂传播后再查一次。若 SQL 线程停止，应保留错误，而不是把“稍等”当成固定修复方法。

## 7.8 分层实训与提交

完整流程见[实训 07：数据库复制与恢复验证](./lab-07.md)。

**保底任务**

* 从结构图中指出主库、从库、二进制日志和两个数据卷；
* 根据给定 `SHOW REPLICA STATUS` 判断 IO、SQL 线程；
* 解释为什么从库不能代替备份。

**标准任务**

* 启动一主一从数据库并完成主写从读；
* 生成逻辑备份和 SHA-256 校验；
* 在三项确认后删除 `course_db`，再导入并核对主从数据；
* 运行 `verify.sh`，清理课程项目并保留报告证据。

**挑战任务**

* 新增第二条记录，测量从主库写入到从库可见的时间；
* 比较只备份单库与 `--all-databases` 的范围差异；
* 设计一份“不把备份只放在原云盘”的保存策略。

提交前清理实验环境：

```bash
[ECS] docker compose down
[ECS] rm -rf ~/cloud-course/lab-07
```

提交文件：

```txt
班级_学号_姓名_第07次课_数据库复制与恢复报告.docx
```

只上传一个 Word 到智慧职教“第07次课”。不上传 `.env`、SQL 备份、公网地址、账号、密码或数据库数据卷。

{{reflection-checkpoint:lesson-07-database-ready}}

## 7.9 第七次课小测

{{assessment:lesson-07-check}}

## 7.10 小结

* 复制面向持续变化的数据流，备份面向可独立保存的恢复材料。
* IO 与 SQL 线程都为 Yes，才说明复制机制在捕获时运行。
* 主库写、从库读能验证一条实际业务数据已经复制。
* 误删也会传播，所以从库不能代替独立备份。
* `mariadb-dump` 生成逻辑备份，`mariadb` 客户端负责导入恢复。
* 备份文件、校验值、恢复后的主从查询共同构成恢复证据。

下一次课将把两台后端服务接到 Nginx 负载均衡器之后，观察正常轮询、单后端故障和恢复。

## 7.11 资料来源

以下资料在 2026-07-28 核对：

* [MariaDB：Setting Up Replication](https://mariadb.com/docs/server/ha-and-performance/standard-replication/setting-up-replication)
* [MariaDB Docker Official Image Environment Variables](https://mariadb.com/docs/server/server-management/install-and-upgrade-mariadb/installing-mariadb/binary-packages/automated-mariadb-deployment-and-administration/docker-and-mariadb/mariadb-server-docker-official-image-environment-variables)
* [MariaDB：Using Healthcheck](https://mariadb.com/docs/server/server-management/automated-mariadb-deployment-and-administration/docker-and-mariadb/using-healthcheck-sh)
* [MariaDB：mariadb-dump](https://mariadb.com/docs/server/clients-and-utilities/backup-restore-and-import-clients/mariadb-dump)
* [MariaDB：Restoring Data from Dump Files](https://mariadb.com/docs/server/mariadb-quickstart-guides/mariadb-restore-guide)

{{assistant-invite:lesson-07-finish}}
