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/