如何快速高效备份数据库

如何快速高效备份数据库

测试前备份数据库是保障数据安全、支持快速回滚的基础操作。本文从备份场景思路与流程图实战代码三个维度,讲解在表数据多、数据量大的情况下,如何快速高效地完成备份。


一、为什么测试前要备份?

场景 风险 备份价值
造数测试 批量插入/更新可能写错 一键回滚到测试前状态
接口压测 并发写入可能污染数据 恢复干净环境复测
规则验证 修改配置后需对比 保留原始数据做 diff
环境复用 多人共用测试库 各自备份互不干扰

二、备份方式对比

方式 速度 适用场景 恢复难度
mysqldump 全库 慢(大库) 小库、完整迁移 简单
mysqldump 单表 较快 只改少数表 简单
mysqldump + 压缩 中等 大库、省空间 需解压
物理文件拷贝 最快 可停库、同机 需停库
SELECT INTO OUTFILE 中等 单表导出 CSV 需建表再导入

三、备份思路与流程图

3.1 备份决策流程

小, <1GB
大, >1GB


需要备份
数据量?
mysqldump 全库
只改部分表?
mysqldump 指定表
mysqldump 全库 + gzip
输出 .sql
输出 .sql.gz
保存到备份目录
记录备份信息

3.2 备份执行流程

全库
单库
指定表


开始
读取连接配置
创建备份目录
备份范围?
mysqldump --all-databases
mysqldump db_name
mysqldump db table1 table2
是否压缩?
管道 gzip
直接输出
写入 .sql.gz
写入 .sql
校验文件大小
记录备份路径/时间
结束

3.3 恢复决策流程

.sql
.sql.gz
需要恢复
定位备份文件
文件格式?
mysql < backup.sql
gunzip -c backup.sql.gz | mysql
验证数据
结束

3.4 测试前备份完整流程



测试计划
确定涉及的表
执行备份脚本
备份完成
执行测试
测试通过?
保留备份一段时间
执行恢复
数据回滚


四、实战一:mysqldump 命令行备份

4.1 全库备份(适合小库)

bash 复制代码
# 全库备份,输出到 SQL 文件
mysqldump -h 主机 -P 3306 -u 用户 -p 数据库名 > backup_$(date +%Y%m%d_%H%M%S).sql

# 示例
mysqldump -h rm-xxx.mysql.rds.aliyuncs.com -P 3306 -u kpos -p kkpos_shopping > backup_20250316.sql

4.2 指定表备份(大库推荐)

只备份测试会涉及的表,显著减少时间和文件大小。

bash 复制代码
# 备份指定表
mysqldump -h 主机 -P 3306 -u 用户 -p 数据库名 表1 表2 表3 > backup_tables.sql

# 示例:只备份商品、价格、库存相关表
mysqldump -h rm-xxx.mysql.rds.aliyuncs.com -P 3306 -u kpos -p kkpos_shopping \
  kkpos_product kkpos_product_price kkpos_stock > backup_product_$(date +%Y%m%d).sql

4.3 全库 + 压缩(大库)

bash 复制代码
# 备份并压缩,节省空间
mysqldump -h 主机 -P 3306 -u 用户 -p 数据库名 | gzip > backup_$(date +%Y%m%d).sql.gz

4.4 常用参数说明

参数 含义 建议
--single-transaction 一致性快照,不锁表 InnoDB 必加
--quick 逐行读,不缓存在内存 大表必加
--skip-lock-tables 不锁表 测试库可用
--routines 备份存储过程/函数 按需
--triggers 备份触发器 默认包含

五、实战二:Python 自动化备份脚本

5.1 配置与思路

将连接信息、备份目录、表列表配置化,一键执行备份。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数据库备份脚本 - 测试前快速备份
支持:全库、单库、指定表;可选压缩
"""
import subprocess
import os
from datetime import datetime

# 数据库连接配置(请替换为实际值)
DB_CONFIG = {
    "host": "rm-xxx.mysql.rds.aliyuncs.com",
    "port": 3306,
    "user": "kpos",
    "password": "your_password",
    "database": "kkpos_shopping",
}

# 备份目录
BACKUP_DIR = os.path.expanduser("~/Desktop/DB_Backup")

# 备份模式: "all" | "database" | "tables"
BACKUP_MODE = "tables"

# 指定表备份时的表列表(仅当 BACKUP_MODE="tables" 时生效)
TABLES = [
    "kkpos_product",
    "kkpos_product_price",
    "kkpos_stock",
    "kkpos_order",
]

# 是否压缩
USE_GZIP = True


def ensure_backup_dir():
    """确保备份目录存在"""
    if not os.path.exists(BACKUP_DIR):
        os.makedirs(BACKUP_DIR)
        print(f"创建备份目录: {BACKUP_DIR}")


def build_mysqldump_cmd():
    """构建 mysqldump 命令"""
    base = [
        "mysqldump",
        "-h", DB_CONFIG["host"],
        "-P", str(DB_CONFIG["port"]),
        "-u", DB_CONFIG["user"],
        f"-p{DB_CONFIG['password']}",
        "--single-transaction",
        "--quick",
        "--skip-lock-tables",
        "--default-character-set=utf8mb4",
    ]

    if BACKUP_MODE == "all":
        base.append("--all-databases")
    elif BACKUP_MODE == "database":
        base.append(DB_CONFIG["database"])
    elif BACKUP_MODE == "tables":
        base.append(DB_CONFIG["database"])
        base.extend(TABLES)

    return base


def run_backup():
    """执行备份"""
    ensure_backup_dir()
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    if BACKUP_MODE == "tables":
        suffix = "_".join(TABLES[:2]) + "_" if len(TABLES) <= 2 else "tables_"
    else:
        suffix = ""

    filename = f"backup_{DB_CONFIG['database']}_{suffix}{timestamp}.sql"
    if USE_GZIP:
        filename += ".gz"

    filepath = os.path.join(BACKUP_DIR, filename)

    cmd = build_mysqldump_cmd()

    print("=" * 60)
    print("数据库备份")
    print("=" * 60)
    print(f"模式: {BACKUP_MODE}")
    if BACKUP_MODE == "tables":
        print(f"表: {TABLES}")
    print(f"输出: {filepath}")
    print("=" * 60)

    try:
        with open(filepath, "wb") as f:
            if USE_GZIP:
                import gzip
                proc_dump = subprocess.Popen(
                    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
                )
                proc_gzip = subprocess.Popen(
                    ["gzip", "-c"],
                    stdin=proc_dump.stdout,
                    stdout=f,
                    stderr=subprocess.PIPE,
                )
                proc_dump.stdout.close()
                proc_gzip.communicate()
                if proc_dump.wait() != 0:
                    raise RuntimeError(f"mysqldump 失败: {proc_dump.stderr.read().decode()}")
            else:
                result = subprocess.run(cmd, stdout=f, stderr=subprocess.PIPE, check=True)

        size_mb = os.path.getsize(filepath) / (1024 * 1024)
        print(f"备份完成: {filepath}")
        print(f"文件大小: {size_mb:.2f} MB")
        return filepath

    except subprocess.CalledProcessError as e:
        print(f"备份失败: {e.stderr.decode()}")
        raise
    except Exception as e:
        print(f"备份失败: {e}")
        raise


if __name__ == "__main__":
    run_backup()

5.2 使用步骤

  1. 修改 DB_CONFIG 为实际连接信息。
  2. 设置 BACKUP_MODEall / database / tables
  3. 若为 tables,填写 TABLES 列表。
  4. 执行:python backup_db.py

六、实战三:大表分批备份(流式导出)

当单表千万级时,可分批 SELECT 导出,降低内存压力。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
大表分批备份 - 按主键范围导出,避免 OOM
"""
import pymysql
import csv
import os
from datetime import datetime

DB_CONFIG = {
    "host": "localhost",
    "port": 3306,
    "user": "root",
    "password": "xxx",
    "database": "test_db",
    "charset": "utf8mb4",
    "cursorclass": pymysql.cursors.DictCursor,
}

TABLE = "large_table"
PK_COLUMN = "id"
BATCH_SIZE = 50000
BACKUP_DIR = os.path.expanduser("~/Desktop/DB_Backup")


def get_min_max_id(conn):
    """获取主键范围"""
    with conn.cursor() as cur:
        cur.execute(f"SELECT MIN({PK_COLUMN}) as min_id, MAX({PK_COLUMN}) as max_id FROM {TABLE}")
        row = cur.fetchone()
        return row["min_id"], row["max_id"]


def backup_table_in_batches():
    """分批导出大表到 CSV"""
    os.makedirs(BACKUP_DIR, exist_ok=True)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filepath = os.path.join(BACKUP_DIR, f"{TABLE}_{timestamp}.csv")

    conn = pymysql.connect(**DB_CONFIG)
    min_id, max_id = get_min_max_id(conn)
    print(f"表 {TABLE}: id 范围 {min_id} ~ {max_id}")

    with open(filepath, "w", encoding="utf-8-sig", newline="") as f:
        writer = None
        current = min_id
        total_rows = 0

        while current is not None and current <= max_id:
            with conn.cursor() as cur:
                cur.execute(
                    f"SELECT * FROM {TABLE} WHERE {PK_COLUMN} >= %s ORDER BY {PK_COLUMN} LIMIT %s",
                    (current, BATCH_SIZE),
                )
                rows = cur.fetchall()
                if not rows:
                    break

                if writer is None:
                    writer = csv.DictWriter(f, fieldnames=rows[0].keys())
                    writer.writeheader()

                for row in rows:
                    writer.writerow(row)
                total_rows += len(rows)
                current = rows[-1][PK_COLUMN] + 1

            print(f"已导出 {total_rows} 行...")

    conn.close()
    print(f"完成: {filepath}, 共 {total_rows} 行")
    return filepath


if __name__ == "__main__":
    backup_table_in_batches()

七、实战四:恢复操作

7.1 恢复 .sql 文件

bash 复制代码
# 恢复未压缩的 SQL
mysql -h 主机 -P 3306 -u 用户 -p 数据库名 < backup.sql

# 恢复前先清空库(谨慎!)
mysql -h 主机 -u 用户 -p -e "DROP DATABASE IF EXISTS kkpos_shopping; CREATE DATABASE kkpos_shopping;"
mysql -h 主机 -u 用户 -p kkpos_shopping < backup.sql

7.2 恢复 .sql.gz 文件

bash 复制代码
gunzip -c backup.sql.gz | mysql -h 主机 -P 3306 -u 用户 -p 数据库名

7.3 Python 恢复脚本

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""从备份文件恢复数据库"""
import subprocess
import sys

DB_CONFIG = {
    "host": "localhost",
    "port": 3306,
    "user": "root",
    "password": "xxx",
    "database": "kkpos_shopping",
}

BACKUP_FILE = sys.argv[1] if len(sys.argv) > 1 else "backup.sql"

def restore():
    if BACKUP_FILE.endswith(".gz"):
        cmd = f"gunzip -c {BACKUP_FILE} | mysql -h {DB_CONFIG['host']} -P {DB_CONFIG['port']} -u {DB_CONFIG['user']} -p{DB_CONFIG['password']} {DB_CONFIG['database']}"
        subprocess.run(cmd, shell=True)
    else:
        cmd = [
            "mysql", "-h", DB_CONFIG["host"], "-P", str(DB_CONFIG["port"]),
            "-u", DB_CONFIG["user"], f"-p{DB_CONFIG['password']}",
            DB_CONFIG["database"],
        ]
        with open(BACKUP_FILE, "r") as f:
            subprocess.run(cmd, stdin=f, check=True)
    print("恢复完成")

if __name__ == "__main__":
    restore()

八、大表备份优化建议

策略 说明
只备份涉及的表 避免全库,显著缩短时间
--single-transaction InnoDB 一致性快照,不锁表
--quick 大表不缓存在内存
gzip 压缩 减少磁盘占用,网络传输更快
分批导出 千万级表用脚本分批 SELECT
避开业务高峰 备份有 IO 压力,尽量低峰执行

九、备份信息记录

建议在备份后记录:时间、文件路径、大小、涉及表,便于后续恢复。

python 复制代码
# 备份完成后追加到日志
log_line = f"{datetime.now().isoformat()} | {filepath} | {size_mb:.2f}MB | {','.join(TABLES)}\n"
with open(os.path.join(BACKUP_DIR, "backup_log.txt"), "a") as f:
    f.write(log_line)

十、总结

  1. 测试前必备份:造数、压测、规则验证前先备份涉及表。
  2. 大库用指定表:只备份会改动的表,节省时间和空间。
  3. 加压缩:大库用 gzip,减少存储和传输成本。
  4. 加 --single-transaction:InnoDB 不锁表,保证一致性。
  5. 记录备份信息:便于快速找到并恢复对应备份。

掌握以上思路与脚本,可以快速、安全地完成测试前数据库备份与恢复。

相关推荐
七夜zippoe3 小时前
MongoDB聚合框架与性能优化实战指南
数据库·python·mongodb·性能优化·聚合框架
聆风吟º4 小时前
金仓数据库 SQL 防火墙:内核级防护,筑牢 SQL 注入安全防线
数据库·sql·安全·金仓·kingbasees
码以致用4 小时前
StarRocks的向量数据库能力
数据库·ai
2501_945423549 小时前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
gameboy03110 小时前
从MySQL迁移到PostgreSQL的完整指南
数据库·mysql·postgresql
xdl259910 小时前
Spring Boot中集成MyBatis操作数据库详细教程
数据库·spring boot·mybatis
回到原点的码农10 小时前
Spring Data JDBC 详解
java·数据库·spring
zb2006412011 小时前
Spring Boot 实战:轻松实现文件上传与下载功能
java·数据库·spring boot
CSharp精选营11 小时前
SQL Server安装避坑:这8个奇葩报错你遇到过几个?
数据库·sql server·安装指南·避坑