目录

MySQL 数据正确迁移指南:多场景方案详解

在业务迭代过程中,MySQL 数据库迁移是常遇到的需求——可能是服务器升级、云服务迁移,也可能是跨版本升级或业务拆分。但迁移稍有不慎就会导致数据丢失、业务中断。

今天就结合实际经验,聊聊不同场景下的正确迁移姿势,供大家学习参考

注意:数据迁移存在一定风险,请提前备份 mysql 数据,以免操作失误导致数据丢失

不同的业务规模、数据量和环境,适配的迁移方式完全不同。

一、基础方案:mysqldump 工具迁移

mysqldump 是 MySQL 自带的备份迁移工具,支持全量导出导入,适合中小型数据库(数据量<10GB),优点是操作简单、兼容性强,缺点是大数据量下效率较低。

步骤 1:导出源数据库全量数据,包含表结构和数据,同时锁定表避免导出过程中数据不一致(生产环境建议在低峰期执行)。

# 导出命令:-u 用户名 -p 密码 -d 仅表结构 -t 仅数据,--single-transaction 避免锁表
mysqldump -u root -p123456 --single-transaction --databases test_db > test_db_backup.sql

步骤 2:将导出的 SQL 文件传输到目标服务器,可通过 scp 命令或工具传输。

scp test_db_backup.sql root@目标服务器IP:/home/backup/

不一定要用 scp,有很多种上传方式,比如 rz/sz,还有 ssh 可视化界面都是可以的

步骤 3:在目标数据库导入数据,确保目标数据库已创建(若未创建需先执行 CREATE DATABASE test_db;)。

mysql -u root -p123456 test_db < /home/backup/test_db_backup.sql

步骤 4:验证数据,对比源库和目标库的表数量、核心表数据量是否一致。

我用 GO 写两个例子,可以实现自动化导出导入(借助 database/sql 包和 os/exec 包调用命令):

参考代码:

package main

import (
	"log"
	"os/exec"
)

// 导出数据库
func dumpDB(user, pwd, dbName, outputPath string) error {
	// 构建 mysqldump 命令
	cmd := exec.Command("mysqldump",
		"-u"+user,
		"-p"+pwd,
		"--single-transaction",
		"--databases", dbName,
		"-r", outputPath)
	// 执行命令并捕获输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Printf("导出失败:%s", output)
		return err
	}
	log.Printf("数据库 %s 导出成功,路径:%s", dbName, outputPath)
	return nil
}

// 导入数据库
func importDB(user, pwd, dbName, sqlPath string) error {
	// 构建 mysql 命令
	cmd := exec.Command("mysql",
		"-u"+user,
		"-p"+pwd,
		dbName,
		"-e", "source "+sqlPath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Printf("导入失败:%s", output)
		return err
	}
	log.Printf("数据库 %s 导入成功", dbName)
	return nil
}

func main() {
	// 配置信息
	user := "root"
	pwd := "123456"
	dbName := "test_db"
	outputPath := "./test_db_backup.sql"

	// 导出
	err := dumpDB(user, pwd, dbName, outputPath)
	if err != nil {
		log.Fatal(err)
	}

	// 这里可添加文件传输逻辑,例如通过 sftp 传输到目标服务器

	// 导入(目标服务器执行时配置对应路径)
	// err = importDB(user, pwd, dbName, outputPath)
	// if err != nil {
	// 	log.Fatal(err)
	// }
}

二、高效方案:直接迁移 data 目录

当数据量极大(10GB 以上)时,mysqldump 效率低下,直接迁移 MySQL 的 data 目录更高效。

核心前提:源库和目标库的 MySQL 版本、操作系统版本必须一致,否则会出现兼容性问题。

步骤 1:停止源库和目标库的 MySQL 服务,避免数据写入导致文件损坏。

# CentOS 系统
systemctl stop mysqld
# Ubuntu 系统
systemctl stop mysql

这个步骤非常关键,必须停止源数据库,否则迁移将失败

步骤 2:找到 MySQL 的 data 目录路径,可通过配置文件 my.cnf 中的 datadir 字段查看。

cat /etc/my.cnf | grep datadir
# 通常路径为 /var/lib/mysql

步骤 3:复制源库的 data 目录到目标服务器的相同路径,注意保留文件权限(用 -a 参数保持权限一致)。

scp -r /var/lib/mysql root@目标服务器IP:/var/lib/

这里和上个方案的上传是一样的,不一定要用 scp,有很多种上传方式

步骤 4:修改目标服务器 data 目录的所属用户和组为 mysql,避免权限不足。

chown -R mysql:mysql /var/lib/mysql

建议操作,虽然很多时候不需要修改权限也可以启动

步骤 5:启动目标库的 MySQL 服务,验证数据是否正常访问。

systemctl start mysqld
# 登录验证
mysql -u root -p123456 -e "SELECT COUNT(*) FROM test_db.user;"

启动过程中可以查看日志,失败会有具体的失败原因,一般如果版本一致、有提前停止源库,都不会启动失败

三、便捷方案:云数据库迁移

该方案仅限云数据库

现在很多业务使用云数据库(如阿里云 RDS、腾讯云 CDB),云厂商通常提供专属迁移工具,操作更简单且稳定性高,支持本地库迁移到云库、跨云库迁移。

以阿里云 RDS 迁移为例

步骤 1:登录阿里云控制台,进入 RDS 实例,找到「数据迁移」模块,创建迁移任务。

步骤 2:选择迁移源类型(本地 MySQL、其他云 MySQL 等),填写源库地址、端口、用户名、密码和待迁移数据库。

步骤 3:配置迁移选项,选择「全量迁移」或「全量+增量迁移」,增量迁移可保障迁移过程中业务不中断。

步骤 4:执行预检查,修复检查不通过的问题(如源库权限不足、网络不通),然后启动迁移任务。

步骤 5:迁移完成后,验证数据一致性,切换业务连接到云数据库。

也可以提工单给厂商,询问是否可以支持帮忙迁移

四、不中断业务:增量迁移

核心业务无法停机时,需采用「全量+增量」迁移方案:先迁移全量数据,再通过 binlog 同步增量数据,最后切换业务。

步骤 1:开启源库的 binlog 功能,修改 my.cnf 配置并重启 MySQL。

# my.cnf 配置
log_bin = /var/lib/mysql/mysql-bin
binlog_format = ROW  # 推荐 ROW 格式,记录数据行变化
server-id = 1  # 源库和目标库 server-id 必须不同
systemctl restart mysqld

步骤 2:导出源库全量数据,同时记录导出时的 binlog 文件名和位置(用于后续增量同步起点)。

mysqldump -u root -p123456 --single-transaction --master-data=2 --databases test_db > test_db_backup.sql
# --master-data=2 会在 SQL 文件中记录 binlog 信息

步骤 3:导入全量数据到目标库(操作同 mysqldump 导入步骤)。

步骤 4:用 GO 实现 binlog 增量同步,参考例子

package main

import (
	"database/sql"
	"log"

	"github.com/go-sql-driver/mysql"
	"github.com/siddontang/go-mysql/canal"
)

// 自定义 binlog 处理逻辑
type MyEventHandler struct {
	canal.DummyEventHandler
	db *sql.DB
}

// 处理插入事件
func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
	switch e.Action {
	case "INSERT":
		log.Printf("插入数据:表 %s,数据 %v", e.Table.Name, e.Rows[0])
		// 这里编写插入到目标库的逻辑
	case "UPDATE":
		log.Printf("更新数据:表 %s,旧数据 %v,新数据 %v", e.Table.Name, e.Rows[0], e.Rows[1])
		// 这里编写更新到目标库的逻辑
	case "DELETE":
		log.Printf("删除数据:表 %s,数据 %v", e.Table.Name, e.Rows[0])
		// 这里编写删除目标库数据的逻辑
	}
	return nil
}

func main() {
	// 目标库连接
	cfg := mysql.Config{
		User:   "root",
		Passwd: "123456",
		Net:    "tcp",
		Addr:   "目标服务器IP:3306",
		DBName: "test_db",
	}
	db, err := sql.Open("mysql", cfg.FormatDSN())
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 配置 canal 连接源库(binlog 同步)
	config := canal.NewDefaultConfig()
	config.Addr = "源服务器IP:3306"
	config.User = "root"
	config.Passwd = "123456"
	config.Database = "test_db"
	// 从全量导出时记录的 binlog 位置开始同步
	config.StartPosition = canal.Position{
		Name: "mysql-bin.000001", // 替换为实际 binlog 文件名
		Pos:  154,                // 替换为实际位置
	}

	c, err := canal.NewCanal(config)
	if err != nil {
		log.Fatal(err)
	}

	// 设置事件处理器
	c.SetEventHandler(&MyEventHandler{db: db})

	// 开始同步
	log.Println("开始增量同步 binlog...")
	err = c.Run()
	if err != nil {
		log.Fatal(err)
	}
}

步骤 5:待增量同步追上源库后,停止业务,切换连接到目标库,完成迁移。

如果想要不停业务迁移,那么只能在业务上实现双写逻辑,同时写旧表和新表,等到增量同步追上源库,新旧表数据一致后,在停止旧表的写,切换读新表即可

五、高风险场景:跨版本迁移

跨版本迁移(如 5.7 迁移到 8.0)存在兼容性问题(如密码加密方式、SQL 语法变化),严禁直接迁移 data 目录,推荐「mysqldump 全量+增量」或专用工具。

关键注意事项

  • 先在测试环境搭建目标版本 MySQL,导入数据测试兼容性,修复报错(如过时函数、索引类型问题)。

  • 5.7 迁移到 8.0 时,需注意密码加密方式:8.0 默认 caching_sha2_password,5.7 为 mysql_native_password,可在目标库配置文件中临时设置 default_authentication_plugin=mysql_native_password,导入后再修改。

  • 使用 mysqldump 导出时,添加 –compatible=目标版本 参数,如 –compatible=mysql8.0。

分享一次跨版本迁移流程(代码迁移):

这是一个非常漫长但比较可靠的过程

表迁移方案 描述
1.  补充单元测试 用来验证修改后代码的可靠性
2.  修改代码 双写 + 双读 + 数据一致性校验 ● 双写: 同时写新旧表 ● 双读+数据一致性校验:查询时同时查新旧数据,并且进行数据一致性校验
3.  新代码 单元测试、自测、测试、灰度测试、上线
4.  异步任务迁移历史数据 只迁移上线前的历史数据,比如 3.01上线,旧表存在完整数据,新表存在 3.01 之后数据,缺少3.01之前数据
5.  新旧表数据一致性校验 据迁移完毕后,对新旧表数据一致性校验,排错
6.  废弃 废弃旧表,废弃历史代码,废弃双写、双读、数据一致性校验

常见问题

Q1. 导出后导入报错「表已存在」

原因:SQL 文件中包含 CREATE TABLE 语句,目标库已存在同名表。

解决:导出时添加 –no-create-info 参数仅导出数据,或导入前删除目标库同名表。

Q2. 直接迁移 data 目录后 MySQL 启动失败

排查方向:

① 源库和目标库版本/系统不一致;

② data 目录权限错误,执行 chown -R mysql:mysql /var/lib/mysql 修复;

③ 日志文件损坏,删除 data 目录下的 .err 日志文件后重试。

Q3. 增量同步 binlog 时数据不一致

原因:binlog 格式设置为 STATEMENT,部分 SQL 语句(如 NOW())执行结果依赖上下文。

解决:将 binlog_format 设为 ROW 格式,记录数据行实际变化。

Q4. 跨版本迁移密码登录失败

5.7 迁移到 8.0 时,目标库用户密码加密方式不兼容。

解决:在目标库执行 ALTER USER ‘root’@’localhost’ IDENTIFIED WITH mysql_native_password BY ‘123456’; 重置密码加密方式。

总结

mysql 数据库迁移在开发者应该不多见,但是也是存在的一类情况,针对不同场景的数据库迁移有不同的技术方案,

如果是数据量比较少,那么建议使用 mysqldump 工具,

如果是版本一致,数据比较大,并且允许停机操作,那么推荐直接停机迁移 data 目录

如果是跨版本迁移,那么建议使用方案五

总而言之,数据库迁移最重要的一点:数据迁移存在一定风险,请提前备份 mysql 数据,以免操作失误导致数据丢失

如果大家有其他迁移的可靠经历,欢迎在评论区交流~

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/mysql-export-import-data/

备用原文链接: https://blog.fiveyoboy.com/articles/mysql-export-import-data/