☰
MySQL命令行导出数据库实战:mysqldump参数详解与备份恢复
2026/10/2 9:16:38 网站建设 项目流程

1. 为什么我坚持用命令行导出数据库

先交代一下背景。我接手过不少项目的数据库运维,发现一个现象:很多开发同学用 Navicat 或者 DataGrip 这类图形化工具用得飞起,鼠标点几个按钮就能把库导出成 SQL 文件,看起来挺方便。但真到了生产环境,尤其是 Linux 服务器上,没有图形界面、没有 GUI 工具、带宽有限,甚至有时候你连 FTP 都传不出去,这时候唯一能依靠的就是命令行。

我自己第一次被命令行“逼上梁山”是在多年前,当时负责一个电商系统的库存库备份。那个库不大,也就 2GB 左右,但 Navicat 导出到一半就断,重试了三次都不行。后来到服务器上用 mysqldump 一次性成功,压缩之后才 300 多MB,用 scp 拉下来也就几分钟的事。从那次之后,但凡涉及数据库备份、迁移、导出,我第一反应永远是命令行,图形工具只是辅助。

这篇文章要讲清楚的,就是 MySQL 命令行导出数据库的完整实战。如果你属于下面这几类人,建议认真看完:

  • 刚接触 MySQL,不知道除了图形界面还能怎么把数据弄出来;
  • 已经在用 mysqldump,但只会背一条最简单的命令,遇到只导结构、只导某张表、只导某几个字段这种需求就卡住;
  • 需要在服务器上做定时备份,想搞一个能直接跑起来的脚本;
  • 遇到过导出后中文乱码、权限报错、导入时数据类型对不上这类问题,想搞明白根因。

我会从原理讲到实操,再给出几种不同场景的命令组合,最后补充常见的坑和排查思路。内容不绕弯子,每一条命令都是我实际跑过的。

2. mysqldump 工作机制:它在底层到底做了什么

很多人用 mysqldump 的时候是“背命令”的心态,知道mysqldump -u root -p123456 dbname > backup.sql能导出,但不知道为什么是这么写的,也不知道导出文件里那些CREATE TABLE、LOCK TABLES、INSERT INTO是什么意思。

先从底层拆一下。

mysqldump 本质上是一个客户端程序,它通过 MySQL 协议和你指定的服务器建立连接,然后执行一系列操作,最终把结果输出到标准输出(stdout)。之所以命令末尾会有>重定向,就是因为它默认是往屏幕上打印,你用重定向符号把输出接进文件里。

导出过程中它做了这几件关键的事情:

  1. 获取表结构信息。它调用SHOW CREATE TABLE获取每张表的建表语句,保证你在另一台机器上导入时能原样重建表结构,包括字段类型、默认值、索引、外键约束、字符集。
  2. 读取表数据。默认情况下是逐行SELECT * FROM 表名读取,然后把结果拼成一条条INSERT INTO语句写入导出文件。
  3. 处理一致性。如果你不指定任何参数,mysqldump 会默认加上LOCK TABLES,也就是在导出当前表的时候把这张表锁住,防止导出过程中数据发生变化,保证导出的是一个时间点的快照。如果加上--single-transaction,则会改用 InnoDB 的事务隔离机制,通过START TRANSACTION WITH CONSISTENT SNAPSHOT拿到一致性的快照,不需要锁全表。
  4. 记录 binlog 位置信息(可选)。某些场景下你需要导出文件里带上 binlog 文件名和位置,方便做主从复制,这时候用--master-data参数。

理解了这个机制,再看命令就容易了。mysqldump -u 用户名 -p 密码 数据库名这几个部分是建立连接用的,后面跟着的才是控制导出行为的参数。

提示:MySQL 8.0 开始,mysql_native_password插件默认被弃用,如果你是 8.0 以上版本,连接时注意用户使用的认证插件。早期版本导出的用户连接不上新库,多半跟这个有关。

另外两个常见的“怪现象”也能解释通了。

第一,为什么导出的 SQL 文件打开后看到很多/*!40101 SET ... */这样的注释?这是 MySQL 的版本条件执行语法。40101表示该行在 MySQL 版本高于 4.01.01 时生效。这样做的好处是同一个导出文件,在低版本和高版本 MySQL 中导入时都能自动跳过不支持的语法,提高兼容性。

第二,为什么导出的文件很大反而比原来的库小很多?因为 SQL 文件是文本文件,里面是插入语句,而原始表数据在磁盘上可能是 InnoDB 页面格式,每页 16KB,存在碎片、填充、索引空间,文本表达未必比二进制多。加上压缩后变化更大,后面我会给一个压测过的实际数据。

3. 从零到一:最常用的导出命令与参数拆解

3.1 单库导出:基础命令的参数含义

先来最基本的命令:

mysqldump -h 127.0.0.1 -P 3306 -u root -p mydb > mydb.sql

执行后会提示输入密码,密码不会显示在命令行里,这是安全习惯,别把密码直接写在命令里,因为history会记录下来。

这条命令做了什么?连接本机 3306 端口的 MySQL,用 root 身份,导出 mydb 整个库的所有表结构和数据,输出到 mydb.sql。

有几个细节值得展开:

-h什么时候该写,什么时候可以不写。默认情况下 mysqldump 连接的是 localhost,会走 Unix Socket,而不是 TCP/IP。你在本机执行,不写-h也很快;但只要涉及网络连接,就必须写-h指定主机。这里有个常见的坑:服务器上 MySQL 没开 socket 文件,或者 socket 路径不一致,会报Can't connect to local MySQL server through socket。这时你可以强制走 TCP,用--protocol=tcp -h 127.0.0.1。

-P是端口,小写-p是密码。大写 P 后面跟数字,小写 p 后面跟密码字符串,两者写法非常容易混,我见过不止一个同事把大小写搞反,报错报得莫名其妙。

3.2 只导结构或只导数据

有时候你不想把所有东西都导出来,比如要迁移到新环境,表结构已经建好了,只需要数据;或者反过来,只要建表语句,不想带数据。

# 只导表结构,不导数据 mysqldump -u root -p -d mydb > mydb_schema.sql # 只导数据,不导表结构 mysqldump -u root -p -t mydb > mydb_data.sql

-d是--no-data的简写,-t是--no-create-info的简写。这两个参数使用频率非常高,尤其是做增量环境同步时,经常需要“结构全量+数据部分”的组合。

3.3 多库导出与单表导出

导出多个库不需要写多个命令,用--databases参数即可:

mysqldump -u root -p --databases db1 db2 db3 > multi.sql

注意--databases和直接跟在命令后面的单库名是有区别的。加了--databases,导出文件里会包含CREATE DATABASE IF NOT EXISTS和USE db1语句,导入时不需要手动创建库;不加则只有表和数据的语句,导入前必须确保目标库存在。

单表导出也很常见:

mysqldump -u root -p mydb users > users.sql

表名直接接在库名后面。如果要导出多张表,可以继续追加:

mysqldump -u root -p mydb users orders > users_orders.sql

这个方式适合只同步大库中的部分核心表,比如订单库几十 GB,每天只需要把 orders 和 order_items 两张表导出做统计,就不需要动整个库。

3.4 带条件的部分数据导出

这个需求也很常见,比如只导出某个时间段的订单、只导出某个用户分组的数据。mysqldump 提供了--where参数:

mysqldump -u root -p mydb orders --where="create_time >= '2024-01-01' AND create_time < '2025-01-01'" > orders_2024.sql

注意--where的 SQL 条件要符合你的目标 MySQL 版本的语法。有一次我在条件里用了 MySQL 8.0 才支持的写法,结果拿到 5.7 的实例上导入直接语法错误。这是个真实教训,导出的目标环境版本你必须在写命令前确认。

3.5 压缩导出:解决大库导出慢、文件大的方案

大库导出最大的痛点是文件太大、写盘慢、传输出慢。解决方案很简单——导出时直接压缩。

mysqldump -u root -p mydb | gzip > mydb.sql.gz

数据经管道直接走 gzip 压缩,不需要先把未压缩的 SQL 文件写到磁盘再压缩,省一份磁盘空间和时间。实际项目中,一个 5GB 的 InnoDB 库,导出的 SQL 文件大约 3.8GB,gzip 压缩后不到 700MB。压缩耗时大约在 20 秒到 1 分钟之间,取决于 CPU 性能,但换来的是传输时间大幅缩短。

解压导入时用:

gunzip < mydb.sql.gz | mysql -u root -p mydb

3.6 核心参数对照表

我刚学 mysqldump 时最烦的就是一堆参数记不住,这里列一个高频参数对照表,建议收藏:

参数简写作用典型使用场景
--all-databases-A导出所有库全实例迁移、整机备份
--databases-B导出指定多个库多库备份
--no-data-d只导结构新环境初始化 DDL
--no-create-info-t只导数据数据迁移
--single-transaction无InnoDB 一致性快照,不锁表在线备份
--lock-tables-l导出前锁定所有表MyISAM 表备份
--where-w按条件导出部分数据导出
--routines-R包含存储过程和函数迁移完整库
--triggers无包含触发器迁移完整库
--events-E包含事件调度器迁移完整库
--master-data无记录 binlog 位置搭建从库
--set-gtid-purged无控制 GTID 记录MySQL 8.0 主从复制

其中--routines、--triggers、--events是很多人忽略的。默认情况下 mysqldump 不导出存储过程、函数、触发器和事件,如果你只执行裸命令mysqldump -u root -p dbname > out.sql,这些数据库对象全部丢失。等你在新环境跑起来发现业务报错“存储过程不存在”,那就晚了。我的习惯是:只要目标是“完整迁移一个库”,这三个参数一定带上。

4. 只导数据还是连结构一起?不同导出方案的取舍

mysqldump 不是唯一的导出方案。根据不同场景,我在项目里会换用不同的工具和方式,这里对比一下,方便你按需选型。

4.1 mysqldump:通用性最强

优点:跨版本兼容性最好、逻辑备份人类可读、最关键的是——它是标准命令行工具,所有 MySQL 环境都自带,不需要额外安装。缺点:速度一般,尤其是大数据量时,单线程逐行读取再生成 INSERT 语句,性能有瓶颈。

适用场景:中小库(5GB 以下)、迁移后可能需要人工检查数据的场景、定时备份脚本。

4.2 mysqlpump:多线程并行导出

MySQL 5.7 开始提供了 mysqlpump,最核心的改进是支持并行导出,速度比 mysqldump 快不少。

mysqlpump -u root -p --parallel-schemas=4:db1,db2 -B mydb > mydb_pump.sql

--parallel-schemas可以指定并发线程数。我在一个 20GB 的库上做过对比,mysqldump 大约跑了 12 分钟,mysqlpump 并行度调到 8,时间压缩到 4 分半。不过 mysqlpump 的导出文件格式跟 mysqldump 略有差异,导入时兼容性也稍弱,如果你下游的工具只认 mysqldump 的输出,就要权衡一下。

4.3 SELECT INTO OUTFILE:快速导出为 CSV/TXT

如果需要导出结果给数据分析、报表系统用,直接用 SQL 语句导出为文本,也是一种“导出数据库”的姿势。

mysql -u root -p -e "SELECT id, user_name, amount FROM mydb.orders WHERE create_time >= '2024-01-01'" mydb > orders.csv

或者用 MySQL 原生的 OUTFILE:

SELECT id, user_name, amount FROM orders INTO OUTFILE '/tmp/orders.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';

注意 OUTFILE 有几个硬性限制:MySQL 进程必须有操作系统层面的文件写入权限;secure_file_priv变量如果配置了,只能导出到指定目录;导出的文件在 MySQL 服务器本机,不在客户端。这点经常搞混,有人本机执行 OUTFILE 却在自己电脑上找文件,当然找不到。

4.4 冷备份:直接复制数据目录

极端场景下,比如要冷迁移整个 MySQL 实例,物理备份反而是最快的。步骤是停库,把整个datadir打包压缩,传到目标机器,初始化目录权限,再启库。

# 停库 systemctl stop mysqld # 打包数据目录(假设 datadir=/var/lib/mysql) tar zcf mysql_datadir_backup.tar.gz /var/lib/mysql # 启动 systemctl start mysqld

优点:速度最快,几乎没有逻辑层的转换开销。缺点:必须停机;目标实例的版本、系统架构、路径结构都可能影响可恢复性;如果要做部分表恢复会非常痛苦。

4.5 选型决策逻辑

我个人在处理导出问题时,会先问自己三个问题:

  • 导出去做什么用?——如果是交给别人导入环境,用 mysqldump/mysqlpump 的逻辑备份;如果是给数据分析团队拉数据,用 OUTFILE 导 CSV。
  • 数据量有多大?——10GB 以内无脑 mysqldump;10GB 以上考虑 mysqlpump 并行;百 GB 级别就要规划逻辑备份的时间窗口,或者考虑物理备份/其他方案。
  • 是否允许锁定?——在线业务库,必须用--single-transaction,不能默认的锁表。

这套判断逻辑是我从多次事故里总结出来的。最开始我只用 mysqldump,第一次做 30GB 库的全量导出直接把线上订单接口拖慢了,因为默认锁表,MyISAM 表读都读不了。后来养成了先查询存储引擎再决定参数的职业习惯。

5. 恢复导入:导出只是前戏,能导回去才是完整闭环

很多文章只讲导出不讲导入,但在实际运维中,导出和导入永远是一个整体。你花大半天导出的文件,如果导入时报错,那个滋味比导出失败还难受。这里我先讲最常用的导入方式,再讲典型坑。

5.1 基础导入命令

mysql -u root -p mydb < mydb.sql

如果导出的文件包含CREATE DATABASE语句(即用了--databases参数),目标端不需要先建库,直接:

mysql -u root -p < mydb.sql

注意了,这里的区别很容易被忽略。不加-D指定数据库,直接读文件执行,文件头部的CREATE DATABASE IF NOT EXISTS xxx会自动创建库。但如果导出时没加--databases,文件里没有建库语句,你就得先手动建库:

mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS mydb CHARACTER SET utf8mb4"

5.2 导入速度优化的三把武器

几个优化导入的参数,亲测有效,大文件导入能差出几倍时间:

mysql -u root -p --max_allowed_packet=512M mydb < mydb.sql mysql -u root -p --init-command="SET FOREIGN_KEY_CHECKS=0" mydb < mydb.sql mysql -u root -p --init-command="UNIQUE_CHECKS=0" mydb < mydb.sql

--max_allowed_packet调大,避免单条INSERT ... VALUES (...)因为包太大而报Packet Too Large的错误。FOREIGN_KEY_CHECKS=0在导入期间关闭外键检查,可以避免因为表导入顺序导致的约束校验失败。UNIQUE_CHECKS=0则是关闭唯一索引检查,导入完再恢复,减少索引维护开销。

这里提一个我自己常用的组合拳:先把导入文件里的所有INSERT语句合并成批量插入形式。mysqldump 默认按较大块批量输出,但如果你拿到的文件是单条单条 INSERT(比如某些工具导出的),导入会非常慢。这种情况下可以先直接导,如果慢得离谱,再用脚本把 SQL 文件中的连续 INSERT 合并成多值形式。实测 100 万行数据,单条 INSERT 导入需要 18 分钟,合并后 11 分钟,少了接近一半。

5.3 导入过程中的常见报错定位逻辑

报错一:Unknown database

ERROR 1049 (42000): Unknown database 'mydb'

原因很简单:导出文件没有包含建库语句,而目标库没建。解决:先建库再导入,或者用带--databases导出的文件。

报错二:Table already exists,然后中断

这个我遇到的情况一般是两种:一是目标库已经存在同名表,而导入过程没有DROP TABLE;二是导出文件里每条CREATE TABLE前面没有IF NOT EXISTS,导致已存在的表直接报错。处理方式是在目标端先清库,或者导入前用参数指定:

mysql -u root -p -e "DROP DATABASE IF EXISTS mydb; CREATE DATABASE mydb CHARACTER SET utf8mb4;"

这样最干净,但操作前千万确认你连的是目标库,不是生产库。我见过同事在测试环境写对了,在生产环境写错了库名,一条DROP DATABASE直接把正库干掉。这种操作,建议每次执行前把命令里的库名再读三遍。

报错三:Data too long for column...

导入时数据类型对不上,通常是因为两端表结构定义不一致。这个问题的根源,有时候是当初导出时用的库是旧版本的(比如 varchar 在某些排序规则下对字符长度的计算方式不同)。最稳妥的做法是导出结构和导入结构保持一致,不要只导数据,结构也一起导。

6. 字符集、权限与阻塞:导出时绕不开的三个头疼问题

6.1 字符集导致的乱码问题

导出文件是文本文件,字符集处理不好,导入后中文全变问号。

我的建议是导出和导入两端都明确指定字符集:

mysqldump -u root -p --default-character-set=utf8mb4 mydb > mydb.sql mysql -u root -p --default-character-set=utf8mb4 mydb < mydb.sql

为什么是utf8mb4而不是utf8?因为 MySQL 的utf8实际最多支持 3 字节编码,存不了 emoji 表情和一些特殊字符。而utf8mb4兼容完整的 Unicode,是 MySQL 8.0 的默认字符集。如果你的库建得早,可能是 utf8 或 latin1,导出前先查一下:

SHOW CREATE DATABASE mydb; SHOW CREATE TABLE mydb.users;

查看结果里的DEFAULT CHARSET=字段,然后用对应的字符集导出,可以避开很多乱码。

如果只是你本地查看导出文件内容觉得中文乱码,并不代表文件本身有问题。用编辑器打开时要设置正确的编码格式,比如 VSCode 里那个右下角的编码切换,要选择 UTF-8 而不是 Windows 的 GBK。我第一年做迁移时在这个上面浪费了半个多小时,一度以为数据导坏了,后来发现是编辑器显示问题。

6.2 权限问题:为什么明明能连 MySQL,mysqldump 还是失败

常见报错:

ERROR 1045 (28000): Access denied for user 'backup'@'localhost'

根源在于 mysqldump 需要执行的语句比普通 SELECT 多:它要SHOW VIEW权限拿到视图定义、RELOAD权限做FLUSH TABLES、PROCESS权限查进程列表,如果导出包括存储过程,还要SELECT权限范围内能查到mysql.proc表(不同版本权限体系有差异)。

如果业务账号只是拿来日常查询的,拿它做全量备份几乎必然报权限不足。正确做法是单独建一个备份账号,给它最小化但完整的备份权限:

CREATE USER 'backup'@'localhost' IDENTIFIED BY 'backup_pass'; GRANT SELECT, SHOW VIEW, RELOAD, LOCK TABLES, PROCESS, TRIGGER ON *.* TO 'backup'@'localhost'; FLUSH PRIVILEGES;

注意RELOAD和LOCK TABLES是全局权限,*.*才能授予。每次新建备份任务我都用这个模板,十年了没出过权限问题。

6.3 导出期间会不会影响线上业务

这是被问得最多的问题,答案取决于存储引擎和参数:

  • 如果全是 InnoDB,加--single-transaction即可。它基于 MVCC 快照读取,导出期间不阻塞业务读写,但你拿到的数据是一个时间点的快照,不是实时的。这是“一致但不最新”。
  • 如果是 MyISAM 表,--single-transaction无效,MyISAM 不支持事务。为了数据一致,必须锁表,锁表期间该表不可写,严重时会读也被阻塞。我在早期一个系统里遇到的情况是:MyISAM 订单表完全没做备份,我加锁导出,结果线上正好有人在下单,直接等了十几秒,导致接口超时报警。后来那张表改造为 InnoDB,问题解决。

检查表引擎:

SELECT table_name, engine FROM information_schema.tables WHERE table_schema='mydb';

如果混有 MyISAM 表,最好先和处理方沟通,或者选业务低谷窗口操作。

7. 从手动到定时:一个可直接复用的备份脚本

讲完原理和踩坑,最后聊一下怎么把导出做成自动化。毕竟手动执行 mysqldump 解决不了“天天要备份”的问题。

下面这个脚本我长期在用,逻辑很简单但很稳:定义库名、备份目录、保留天数;导出时压缩;保留最近 N 天,超期的自动删除。

#!/bin/bash # mysql_backup.sh DB_HOST="127.0.0.1" DB_USER="backup" DB_PASS="your_password" DB_NAME="mydb" BACKUP_DIR="/data/mysql_backup" KEEP_DAYS=7 DATE=$(date +%Y%m%d_%H%M%S) FILE="$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz" mkdir -p $BACKUP_DIR mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASS \ --single-transaction --routines --triggers --events \ --databases $DB_NAME | gzip > $FILE if [ $? -eq 0 ]; then echo "[$(date +'%F %T')] Backup success: $FILE" else echo "[$(date +'%F %T')] Backup failed" exit 1 fi find $BACKUP_DIR -type f -name "*.sql.gz" -mtime +$KEEP_DAYS -exec rm -f {} \;

有几个细节说明:

  • -p$DB_PASS这样写会暴露密码在脚本里,所以脚本文件的权限要收紧:chmod 700 mysql_backup.sh,保证只有 root 能读。更安全的做法是用 MySQL 配置文件.my.cnf存连接参数,或者用MYSQL_PWD环境变量(但也要注意进程列表会暴露,看你的安全要求)。
  • --single-transaction这条是脚本的生命线。如果没有它,对于 InnoDB 库,备份等同于锁表,对于线上高并发系统几乎不可接受。
  • 定时任务建议加锁,避免上次备份还没跑完,下一个 cron 又启动了:
* 2 * * * /usr/bin/flock -n /tmp/mysql_backup.lock /data/mysql_backup.sh

flock拿不到锁就跳过本次执行,保证同一时间只有一个备份任务在跑。

  • 有条件的话,备份后的文件再往异地传一份,最简单的用 rsync 或者 scp 同步到另一台机器。本地存一份、异地存一份,双保险。我吃过一次本地磁盘损坏的亏,备份文件跟着一起没,从那之后异地这份就没断过。

8. 导出文件怎么验证:导入前的最后一道保险

我觉得这是最容易忽略、但也最救命的一步。导出完别急着收工,至少花几十秒验证一下。

8.1 检查文件完整性

# 查看文件大小 ls -lh mydb.sql.gz # 查看压缩包内的 SQL 是否正常 gzip -t mydb.sql.gz

gzip -t只能证明压缩包没有损坏,不能证明 SQL 内容没问题。

8.2 抽样检查关键表

如果你导出的是整个库,可以在目标环境里先做一次快速导入测试,或者至少把文件解压,grep 出某张核心表的建表语句和数据条数做个对比:

grep "CREATE TABLE \`users\`" mydb.sql grep -c "INSERT INTO \`users\`" mydb.sql

INSERT 的数量不等于行数,因为一条 INSERT 可能包含多行值,但文件里有内容总比没有强。更严格的做法:

# 解压后用 tail 看文件结尾 gunzip -c mydb.sql.gz | tail -20

正常的 mysqldump 导出文件在最后会有:

-- Dump completed on 2025-01-01 12:00:00

看到这行,通常说明导出过程完整跑完了。如果没有,大概率是中途断掉了或者内存被杀,文件不完整。

8.3 在隔离环境做一次导入演练

条件允许的话,我的建议是每次重大变更前,在本地用 Docker 起一个临时 MySQL 实例,把备份文件导入进去,跑几条关键 SQL 验证数据完整性。不用多复杂:

docker run --name backup_test -e MYSQL_ROOT_PASSWORD=test -d mysql:8.0 docker cp mydb.sql.gz backup_test:/tmp/ docker exec backup_test sh -c "gunzip < /tmp/mydb.sql.gz | mysql -u root -p test"

验证完直接删容器,不占用任何正式资源。这十分钟的演练,能避免你在正式切换环境时发现备份文件有问题而手足无措。

9. 踩过的坑:完整排查链路实录

分享两个我印象最深的真实事故,当时排查的过程很值得参考。

9.1 SSL 连接错误:导出命令直接卡死

有一次在一台新部署的 MySQL 8.0 服务器上执行 mysqldump,命令刚敲完直接报:

ERROR 2026 (HY000): SSL connection error: protocol version mismatch

首先要说明的是,这个报错跟“网络不通”或者“密码错误”是两回事。SSL connection error表示 TCP 连接已经建立了,但 MySQL 服务端和客户端的 TLS 握手阶段处理版本不对。

排查步骤:

  1. 检查客户端版本和服务端版本。那台服务器上 mysqldump 版本是 8.0.33,没问题。
  2. 检查服务端 SSL 配置。看SHOW VARIABLES LIKE 'have_ssl'和ssl_cert、ssl_ca的路径配置。发现ssl_ca路径指向的 CA 文件不存在。
  3. 确认问题根源:服务端声明了 CA 文件,但文件缺失,导致 TLS 证书链验证失败,客户端认为协议版本不匹配。
  4. 临时绕过方案:在 mysqldump 命令中禁用 SSL,--ssl-mode=DISABLED。生产环境这不是最终解决,但能快速恢复备份。
  5. 最终修复:生成正确的 CA 证书并配置到ssl_ca路径,重新加载实例。

这类问题在网上搜出来的答案大多是“加--ssl-mode=DISABLED绕过”,但作为接班的人,你最好搞清楚为什么需要绕过。环境里如果有强制 SSL 的合规要求,绕过只是暂时保业务,长期还是得修证书链路。

9.2 导出文件导入后数据错位

一次迁移任务,导出的 SQL 文件在两个库之间导入,逻辑上看一切正常,但查询结果对不上。排查发现导出的文件缺了--complete-insert参数,而源表字段顺序并不是建表语句里的字段顺序(以前做过 ALTER TABLE 变更),导致部分行的字段顺序对了、但值插入错位。

mysqldump 默认生成的 INSERT 语句是:

INSERT INTO `t` VALUES (1, 'a', 'b');

如果目标表的字段顺序和源表不完全一致,这种写法就是灾难。加上--complete-insert之后:

INSERT INTO `t` (`id`, `col_a`, `col_b`) VALUES (1, 'a', 'b');

字段名写清楚了,顺序错不了的。

从那以后,凡是要跨环境导入的导出,我默认都会加上--complete-insert。这算是一个用事故换来的经验。

10. 如果你用的是 MySQL 8.0:几个必须了解和适配的变化

MySQL 8.0 在权限和默认参数上跟 5.7 相比变化很大,导出实操中有些东西需要重新适应。

密码认证插件。MySQL 8.0 默认认证插件是caching_sha2_password,而 5.7 时代大量驱动和客户端工具用的是mysql_native_password。如果你从 8.0 导出文件,导入回 5.7,或者反过来,很可能遇到认证失败。处理方案是迁移前确认两端认证插件:

SELECT user, host, plugin FROM mysql.user WHERE user='backup';

如果两端插件不一致,可以在 8.0 端把用户调整为旧插件(不推荐长期用),或者更新客户端连接到新版。

GTID 的影响。MySQL 8.0 中 GTID 默认开启。导出时如果不加--set-gtid-purged=OFF,导出文件里会包含SET @@GLOBAL.GTID_PURGED=...。导入时如果目标库的 GTID 状态不允许,会直接报错。从 MySQL 8.0 导出、目标库接 5.7 或者 GTID 配置不一致时,注意手动指定:

mysqldump -u root -p --set-gtid-purged=OFF mydb > mydb.sql

我自己在 8.0 到 8.0 的迁移里,大部分情况反而需要保留 GTID 信息,只要目标实例是全新的、没有历史 GTID,导入没问题。先确认好源和目标的状态,再决定参数。

默认字符集已经是 utf8mb4。8.0 的默认字符集是utf8mb4,导出时即使不指定字符集,文件本身也基本不会乱码。但如果你要导入的目标是 5.7 实例,而那个实例默认字符集是latin1,导入时必须显式指定字符集,否则表内数据按目标库默认字符集解释,中文照样乱码。

sql_mode 变化。8.0 的默认sql_mode更严格,比如包含ONLY_FULL_GROUP_BY和STRICT_TRANS_TABLES。导出的 SQL 文件如果包含以前宽松模式下的 SQL 写法,导入到 8.0 有可能因为严格模式而报错。遇到这种情况,可以在导入会话中调整:

mysql -u root -p --init-command="SET SESSION sql_mode=''" mydb < mydb.sql

注意这是临时措施,正式环境应该修改 SQL 语法本身,但迁移过程中快速通过,这招有用。

11. 最后分享三点长期有用的习惯

以上是导出导入的完整链路。结合这么多年反复踩坑的经验,最后沉淀几句给你。

第一,导出数据库从来不是“跑一条命令”这么简单,你要搞清楚导出的内容、格式、字符集、权限、是否阻塞业务,这五个维度全对了,导出文件才算合格。

第二,备份文件永远不要把鸡蛋放在一个篮子里。本地磁盘一份、异地一份、定期做恢复演练。备份做了三年没人验证过,某天真要恢复时发现备份文件根本导不进去,那比没备份更伤人。我最感谢自己的一次决策就是坚持每月在 Docker 里做一次完整导入演练。

第三,命令别靠背,要靠理解。mysqldump的参数组合不过二十来个,花半天时间把官方文档对应的每一条过一遍,远比每次出问题搜答案更划算。尤其是--single-transaction、--routines、--triggers、--complete-insert、--set-gtid-purged这几个,理解它们背后的机制,你就能覆盖 90% 以上的导出需求。

如果看完这篇文章你只能记住一个动作,我希望是:下次导完数据库,先解压看一眼文件结尾有没有 “Dump completed on”,再做其他操作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询