银河麒麟V10 桌面版上装 MySQL 这件事,最近被好多同事问过。业务方指定数据库必须是 MySQL,而且要主从复制,单独拆开都不复杂,但放到国产 Linux 系统上,坑一个接一个。这篇文章把我从下载安装包到主从复制跑通的完整过程记录下来,命令直接抄,配置直接复制,只要你环境的 CPU 架构和系统版本跟文中一致,基本能一次跑通。
文中的方法不依赖你机器能不能访问外网——只要你能把官网的通用二进制包拷进内网,离线一样能装。对于银河麒麟V10 这种系统源里没有官方 mysql-server 包的环境,这是目前最省心、最可控的一条路。
1. 为什么在银河麒麟V10上装MySQL要先解决“源”的问题
很多人在国产系统上装软件,第一反应就是apt install。但银河麒麟V10 的默认软件源里,mysql-server这个包是不存在的,有的只是mariadb-server。MariaDB 虽然是 MySQL 的分支,但业务方如果明确要求“必须官方 MySQL”,你用 MariaDB 顶上后面解释成本很高;而且有些运维脚本、备份工具、集群方案对 MySQL 官方版本有依赖,混着用很容易出幺蛾子。
另一个常用办法是添加 MySQL 官方 APT 源。MySQL 官网确实提供了mysql-apt-config这种仓库配置包,但它在麒麟系统上能不能顺利装上,完全随缘。我这里遇到过两个典型问题:一是仓库签名校验失败,二是依赖解析冲突。尤其 arm64 架构(飞腾、鲲鹏)的机器,官方 APT 源经常连二进制都找不到。如果你刚好拿到一台基于 Debian 系的 V10 变体,问题会更明显。
所以我的建议很直接:放弃 apt 这条路,用 MySQL 官方针对 Linux 发布的通用二进制包。这个包不依赖系统源,glibc 2.17 以上就能跑,x86_64 和 aarch64 都有对应版本,安装方式就是解压、初始化、配置、启动。它唯一的缺点是初始化、配 systemd 这些事得手动做,但这篇文章就是干这个的。
在动手之前,建议先花二十秒确认两件事:
cat /etc/os-release uname -m第一个命令看系统基于什么发行版,第二个看 CPU 架构。uname -m输出x86_64就用 x86_64 的包,输出aarch64就用 aarch64 的包,这个千万别搞错,否则会直接提示 Illegal instruction 或者无法执行。常见情况是银河麒麟桌面版 V10 基于 Ubuntu 20.04 或 Debian 系列,但这不影响我们用通用二进制包的方案。
三种安装方式的对比,我简单列过一张表:
| 安装方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| apt install mariadb-server | 命令短,装完就能跑 | 不是官方 MySQL,版本和目录结构有差异 | 不挑数据库实现,只求能用 |
| MySQL 官方 APT 仓库 | 自动处理依赖和升级 | 麒麟源兼容性差,arm64 基本没戏 | 基于 Ubuntu 且 x86_64,运气好才顺利 |
| 官方通用二进制包 | 不依赖系统源,架构全,支持离线 | 需要手动初始化和配置服务 | 几乎所有场景,特别是内网离线机 |
我自己现在不管在麒麟、统信 UOS 还是纯 Ubuntu 上装 MySQL,都默认用通用二进制包。这套流程练熟了,换任何 Linux 发行版都是同一套操作,不用每次去研究那个发行版的源里有没有 mysql。
2. 二进制包安装MySQL的完整落地步骤
2.1 下载、解压与目录规划
去 MySQL 官网的 Downloads 页面,选择 MySQL Community Server,然后选 Linux - Generic。官网会提供 tar.xz 压缩包,文件名类似:
mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz mysql-8.0.40-linux-glibc2.17-aarch64.tar.xz下载之前注意一下 glibc 版本需求。最常见的通用包需要 glibc 2.17+,银河麒麟V10 基本都满足。如果真的在很老的系统上碰到version 'GLIBC_2.XX' not found,那就得换低版本 MySQL 或者用该发行版自带的 MariaDB,但我实际遇到这种情况的概率很低。
把包放到/opt目录下解压:
cd /opt tar -xJf mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz mv mysql-8.0.40-linux-glibc2.17-x86_64 /usr/local/mysql我习惯把 MySQL 固定放在/usr/local/mysql,然后通过软链或者 PATH 环境变量来调用。这样后续升级时只需要替换目录内容,业务路径不用变。如果你机器上/usr/local空间不够,放/opt/mysql也完全可以,但后面所有命令里的/usr/local/mysql都要对应改。
接着创建 mysql 系统用户和数据目录:
groupadd mysql useradd -r -g mysql -s /bin/false mysql mkdir -p /data/mysql chown -R mysql:mysql /data/mysql数据目录我没有用默认的/var/lib/mysql,而是单独放在/data/mysql。原因很简单:把数据目录独立出来,方便单独挂载数据盘、做快照、扩容。万一系统盘满了或者系统重装,数据目录还在,恢复成本低很多。
2.2 先写一份实用的 /etc/my.cnf
很多教程是先初始化、再写配置,但我建议反过来。初始化数据目录的时候 mysqld 会读取配置文件里的字符集、排序规则这些参数,如果先初始化再改配置,你可能需要重建数据目录才能让字符集改动完全生效。
创建/etc/my.cnf,内容如下:
[mysqld] basedir=/usr/local/mysql datadir=/data/mysql port=3306 socket=/tmp/mysql.sock pid-file=/data/mysql/mysql.pid # 复制相关(先开好,后面不用返工) server-id=1 log-bin=mysql-bin binlog-format=ROW binlog-expire-log-seconds=604800 # 字符集与时区 character-set-server=utf8mb4 collation-server=utf8mb4_0900_ai_ci default-time-zone='+08:00' # 连接设置 bind-address=0.0.0.0 skip-name-resolve max_connections=1000 [client] port=3306 socket=/tmp/mysql.sock default-character-set=utf8mb4这里有几个参数我先解释一下,后面基础配置章节还会细说:
server-id=1和log-bin=mysql-bin是主从复制的命根子,主库必须开,从库作为主库的备份机可开可不开,但为了以后主从切换方便,建议都开。binlog-format=ROW是我比较推荐的格式,数据一致性最好。虽然日志量比 STATEMENT 大,但现代磁盘和网络基本不在乎这点开销。bind-address=0.0.0.0表示监听所有网卡,主从复制场景必须这么配,否则从库连不上主库。如果 MySQL 和应用在同一台机器,只想本机访问,可以改成127.0.0.1,但主从复制时这句别这样写。
写完后再把目录属主确认一次:
chown -R mysql:mysql /data/mysql2.3 初始化数据目录,拿到 root 临时密码
执行初始化命令:
/usr/local/mysql/bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql--initialize会让 mysqld 帮你建好全新的数据目录,包括 mysql 系统库。初始化完成后,日志里会打印一行 root 临时密码:
[Note] A temporary password is generated for root@localhost: 8HkLp2!x9F把这串临时密码记下来,后面首次登录要用。如果屏幕上没看到,就去数据目录下的.err文件里找:
grep 'temporary password' /data/mysql/mysql.err注意.err文件具体名字和你的主机名相关,也可以直接ls -l /data/mysql/*.err查看。
很多朋友在这里会踩坑:初始化没报错,但启动服务的时候报[ERROR] --initialize specified but data directory exists。这是因为你之前手动执行过初始化,或者datadir目录里已经生成了空文件。解决方法是把/data/mysql清空(确保里面没有重要数据)再重新初始化。
2.4 注册 systemd 服务并设置开机自启
mysqld 自带 mysqld_safe 方式,但桌面版系统我更推荐注册成 systemd 服务,开机自启、日志管理都方便。
创建/etc/systemd/system/mysqld.service:
[Unit] Description=MySQL Server After=network.target [Service] User=mysql Group=mysql Type=simple ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf LimitNOFILE=65535 [Install] WantedBy=multi-user.target然后执行:
systemctl daemon-reload systemctl enable mysqld systemctl start mysqld systemctl status mysqld注意 service 里User=mysql很重要,mysqld 不允许用 root 身份直接跑,如果你不带--user=mysql参数启动,它会自己切到 mysql 用户,但权限配置不对反而容易出问题。显式指定用户是更稳的做法。
启动后再确认一下 3306 端口在监听:
ss -lntp | grep 3306看到LISTEN 0 128 0.0.0.0:3306就说明服务起来了。
2.5 首次登录改密码与安全加固
用刚才记下的临时密码登录:
/usr/local/mysql/bin/mysql -uroot -p登录后第一件事改密码,MySQL 8 要求密码不能太弱,至少 8 位并包含大小写字母、数字和特殊字符:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Root@2024Strong';然后建议把 mysql 命令加入 PATH,后面用起来方便:
echo 'export PATH=/usr/local/mysql/bin:$PATH' > /etc/profile.d/mysql.sh source /etc/profile.d/mysql.sh之后直接敲mysql -uroot -p就能进客户端。
安全加固方面,官方提供了一条交互命令mysql_secure_installation。执行后它会依次问你是否设置密码校验强度、是否移除匿名用户、是否禁用 root 远程登录、是否删除 test 库、是否刷新权限。我的建议是:匿名用户删掉,test 库删掉,root 远程登录禁止,密码校验强度看场景。如果是内网测试环境,密码校验强度可以选低一点,否则你后面建业务账号时随便一个强度不够的密码都会被拒。
3. 基础配置不只是“能连上就行”,这些参数直接影响复制
3.1 端口、监听地址与数据目录
port=3306是 MySQL 默认端口,一般不用改。如果 3306 被别的程序占了,可以换,但改端口意味着所有客户端连接串、防火墙规则、从库的 MASTER_PORT 都要跟着改,没必要就别动。
bind-address=0.0.0.0放开了所有网卡监听。这里有个安全权衡:如果 MySQL 只给本机应用用,127.0.0.1确实更安全;但只要你有主从复制的需求,就必须允许主库被从库访问,这种情况下稳妥做法不是把 bind-address 改成具体 IP,而是先把 MySQL 的账号权限控制好,再从防火墙层面放行指定来源 IP 的 3306 端口。顺序不能反——你先把账号建得乱七八糟,再把端口全放开,那跟裸奔没区别。
还有一个容易忽略的参数是skip-networking。这个参数一旦开启,MySQL 就只监听 unix socket,所有 TCP 连接全部失效。网上很多配置文件模板里会加它做安全优化,但主从复制场景下千万别开。我排查过好几次从库连不上主库的问题,最后发现是某个调优脚本把这个参数加进去了。
3.2 字符集和时区必须落地
字符集用utf8mb4,不要用utf8。MySQL 的utf8实际上是 utf8mb3,最多只能存 3 字节,像 emoji 和一些生僻汉字存不进去,会报Incorrect string value。而utf8mb4是完整的四字节 UTF-8,也是 MySQL 8.0 的默认字符集。
排序规则utf8mb4_0900_ai_ci是 MySQL 8.0 新增的,支持 Unicode 9.0,性能也更好。如果你之后要从 MySQL 5.7 迁移数据,或者和 5.7 实例做主从,建议排序规则统一用utf8mb4_general_ci,兼容性更稳。但如果你都是 8.0 实例,直接用utf8mb4_0900_ai_ci没问题。
时区必须显式设置。很多机器系统时区是 CST(China Standard Time),但 MySQL 的system_time_zone可能不一定是 +08:00。如果你不设置default-time-zone,timestamp 类型的字段存储和展示可能出现 8 小时偏差。在/etc/my.cnf里写死:
default-time-zone='+08:00'也可以写成default-time-zone='Asia/Shanghai',但前提是 MySQL 的时区表已经加载。用+08:00这种偏移量写法最不容易出问题,不依赖时区表。
3.3 先把 binlog 和 server-id 开对,后面复制才不用返工
主从复制的核心就是 binlog,所以从一开始就要把它配置好。log-bin=mysql-bin开启 binlog 后,主库每次数据变更都会写一份二进制日志,从库就是靠拉取这份日志来同步的。
server-id用于标识每台 MySQL 实例的全局唯一 ID。一个复制环里每个实例的 server-id 必须不同,否则从库会分不清日志从哪来。我见过有人图省事,主库从库全用默认的server-id=1,结果 IO 线程一直报错,还以为是网络问题。所以从库的 server-id 一定改成别的数字,比如 2。
binlog-format=ROW是复制时的日志格式。ROW 格式记录的是“哪张表的哪一行的字段从什么值变成了什么值”,比 STATEMENT 格式只记录 SQL 语句要更精确。缺点是 binlog 体积变大、占用磁盘,不过对于现在的机器来说都可以接受。混合模式MIXED是折中,但既然 8.0 已经对 ROW 格式做了很多优化,我倾向于直接用 ROW,省得后面遇到触发器、存储过程复制不一致的诡异问题。
binlog 清理策略我习惯用binlog-expire-log-seconds=604800,代表 7 天。这个值不要太短,尤其主从场景,如果从库因为网络故障断了一两天再恢复,binlog 已经过期被清了,那从库就只能重新做全量备份恢复,非常麻烦。
3.4 连接权限、防火墙和 DNS 解析的关系
建业务账号时,我强烈建议按最小权限来。比如业务需要读写某个库,就只授权那个库:
CREATE USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'App@2024'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'192.168.1.%';这里把host字段限制到内网网段192.168.1.%,比%安全得多。%是所有主机都可以连接,生产环境慎用。
skip-name-resolve我默认打开。它让 MySQL 不再对客户端 IP 做反向 DNS 解析,能明显减少连接耗时。但副作用是,MySQL 里的账号 host 字段只能写 IP 或%,不能写主机名。很多人打开了这个参数后,发现原来用'user'@'localhost'本地登录没问题,但用'user'@'myhostname'连不上,就是这个原因。我的建议是:统一用 IP 规划账号,别混用主机名。
防火墙是主从复制最容易忽略的一环。麒麟桌面版可能没开防火墙,但如果你之前手动开过 firewalld 或者 iptables,得先把 3306 放行。firewalld 下:
firewall-cmd --zone=public --add-port=3306/tcp --permanent firewall-cmd --reload如果用的是 ufw:
ufw allow 3306/tcp检查防火墙规则的命令是:
firewall-cmd --list-all排查的时候最直接的办法是,从库上执行telnet 主库IP 3306,如果能通,说明网络和防火墙没问题;如果超时,问题基本就在防火墙或者云平台安全组上。
4. 主从复制的完整配置流程
4.1 复制链路怎么跑起来的
先花一分钟把复制原理讲清楚,后面排错会顺很多。主库上所有数据变更会按顺序写入 binlog,从库上有两个线程在后台工作:IO 线程负责连接主库,把主库 binlog 里的内容拉到从库,写到从本地的中继日志 relay log;SQL 线程负责读取 relay log,并重放这些日志操作到从库的数据文件里。
打个比方:主库每天写工作日报(binlog),从库雇了一个信使(IO 线程)去拿日报复印件,放到自己桌面上(relay log),再雇一个抄写员(SQL 线程)照着日报内容把数据抄进自己的记录里。只要日报不停、信使和抄写员不请假,两边数据就是一致的。
所以从库的状态看两个线程:Slave_IO_Running: Yes和Slave_SQL_Running: Yes,两个都是 Yes 才表示复制链路健康。
4.2 主库配置与复制账号
主库的/etc/my.cnf中,以下配置必须存在并正确:
server-id=1 log-bin=mysql-bin binlog-format=ROW binlog-expire-log-seconds=604800修改配置后重启:
systemctl restart mysqld然后登录主库,创建专门用于复制的账号。千万别用 root 来复制,权限太大,一旦账号泄露整个库都完蛋。复制账号只需要两个权限:REPLICATION SLAVE和REPLICATION CLIENT。“REPLICATION CLIENT” 是给监控工具用的,如果你不需要监控,只保留 REPLICATION SLAVE 也够。
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'Repl@2024'; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES;关于认证插件多说一句。MySQL 8.0 默认认证插件是caching_sha2_password,如果你的从库也是 8.0,直接用默认插件没问题。但如果从库是 5.7,或者你后面用某些旧版客户端工具连接,就得创建用户时显式指定mysql_native_password,否则会报Authentication plugin 'caching_sha2_password' cannot be loaded。我上面命令里已经加上了,图个省事。
接着查看主库当前的 binlog 位置:
SHOW MASTER STATUS;输出类似:
+------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000001 | 154 | | | | +------------------+----------+--------------+------------------+-------------------+记下File和Position这两个值,后面从库执行 CHANGE MASTER TO 时要用。
4.3 从库配置与初始数据同步
从库的/etc/my.cnf中,server-id 必须和主库不同,其他复制参数保持相似:
server-id=2 log-bin=mysql-bin binlog-format=ROW relay-log=mysql-relay-bin read_only=1relay-log=mysql-relay-bin是设置中继日志前缀,默认叫hostname-relay-bin,显式指定可以避免主机名变化导致日志文件混乱。read_only=1能防止普通应用账号在从库写入数据,避免主从数据不一致。注意它不影响 root 和复制线程,所以 DBA 自己还是可以执行写操作。
修改后重启:
systemctl restart mysqld接下来是主从配置里最容易出问题的一步:初始数据同步。如果主库是全新的空库,可以直接跳到下一节执行 CHANGE MASTER TO。但如果主库已经有业务数据,必须先做一次全量备份,恢复到从库,然后再启动复制。顺序错了,从库会从空状态开始追 binlog,结果就是大量 1062 主键冲突,直接中断。
全量备份用 mysqldump,建议加上--single-transaction,这样可以在 InnoDB 引擎下不锁表完成备份:
/usr/local/mysql/bin/mysqldump -uroot -p \ --single-transaction --routines --triggers --events \ --all-databases --master-data=2 > /tmp/master_backup.sql--master-data=2会把 CHANGE MASTER TO 位置信息作为注释写进备份文件头部,方便你恢复后确认 binlog 位置。也可以不依赖它,直接用上面 SHOW MASTER STATUS 得到的值。
把备份文件传给从库:
scp /tmp/master_backup.sql 192.168.1.20:/tmp/在从库上执行恢复:
/usr/local/mysql/bin/mysql -uroot -p < /tmp/master_backup.sql恢复完成后,如果从库的 read_only 已经生效,你只能以 root 身份导入,但这里实际是在恢复期间直接用 root 执行的,没问题。
4.4 建立复制通道:binlog 位置法
登录从库,执行 CHANGE MASTER TO 指定主库信息:
CHANGE MASTER TO MASTER_HOST='192.168.1.10', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='Repl@2024', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=154;这里的 IP、账号、文件名、位置号全部要和主库实际情况一致。MASTER_LOG_FILE和MASTER_LOG_POS必须对应SHOW MASTER STATUS里的值,位置号写错或者文件写错,IO 线程会直接报错。
然后启动复制:
START SLAVE;查看状态:
SHOW SLAVE STATUS\G重点关注这几行:
Slave_IO_Running: Yes Slave_SQL_Running: Yes Seconds_Behind_Master: 0 Last_IO_Error: Last_SQL_Error:两个Yes+ 延迟为 0,基本就算成功了。验证方法也很简单:在主库建一张测试表插入一条数据,到从库 SELECT 出来,能看到就说明链路正常。
4.5 更省心的方案:GTID 自动定位复制
binlog 位置法比较传统,但有个明显的痛点:如果从库宕机或者网络中断时间长了,需要找到准确的 binlog 和 position 才能重新对齐。GTID(全局事务标识符)方式可以解决这个麻烦。
GTID 会给每个事务分配一个全局唯一的 ID,主从复制不再依靠“文件 + 位置”对齐,而是靠事务 ID 自动对接。配置方法很简单,主从两台机器的/etc/my.cnf都加上:
gtid_mode=ON enforce_gtid_consistency=ON重启后,主从都启用了 GTID。这时从库的 CHANGE MASTER TO 就不需要指定 binlog 文件和位置了,改成:
CHANGE MASTER TO MASTER_HOST='192.168.1.10', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='Repl@2024', MASTER_AUTO_POSITION=1; START SLAVE;MASTER_AUTO_POSITION=1的意思是从库会自动请求主库从自己缺失的 GTID 事务开始同步,不用人工找位置。虽然 binlog 位置法仍然完全可用,但新搭建的环境我强烈建议直接用 GTID。后期如果要主从切换、故障恢复,GTID 能省掉一大半心思。
需要注意一个问题:如果你的主库从还没开 GTID 的状态想改成 GTID,官方要求所有 MySQL 版本都推荐用在线方式平滑开启,但实验环境简单粗暴一点就是主从都停掉、清掉旧的复制关系、改配置重启。第一次搭环境直接在配置阶段就把 GTID 打开,是最省事的。
5. 配置主从复制最容易翻车的几个点
5.1 IO线程一直Connecting:从网络到UUID的排查链路
搭建完成后最让人焦虑的就是执行SHOW SLAVE STATUS\G看到Slave_IO_Running: Connecting,这表示 IO 线程根本连不上主库或者连上但握手失败了。排查顺序我建议从简到繁:
第一步,确认网络通不通。在从库上执行:
telnet 192.168.1.10 3306如果 telnet 不存在,用:
nc -vz 192.168.1.10 3306这一步能过滤掉绝大多数防火墙、网络不通的问题。如果连不通,回到第 3 章检查防火墙和 bind-address。
第二步,确认账号权限。用复制账号手动连接主库:
mysql -urepl -p'Repl@2024' -h192.168.1.10 -P3306能连上但执行SHOW DATABASES;什么都看不到很正常,因为 repl 账号没有普通查询权限。只要能登录,说明账号和密码没问题。
第三步,看详细错误。SHOW SLAVE STATUS\G里Last_IO_Error会给出具体原因。常见报错有:
Master and slave have equal MySQL server UUIDs:这是虚拟机克隆最常见的问题——从库是主库克隆来的,/data/mysql/auto.cnf里保存的 server-uuid 一模一样。这时在从库删除这个文件,重启 mysqld,让它重新生成 UUID:
systemctl stop mysqld rm -f /data/mysql/auto.cnf systemctl start mysqldAuthentication plugin 'caching_sha2_password' reported error:前面说过,创建复制账号时指定mysql_native_password,或者升级客户端驱动。Access denied for user 'repl'@'...':复制账号的 host 范围没覆盖从库 IP。CREATE USER 'repl'@'%'的%覆盖所有 host,如果你把它写成了具体 IP,就检查从库实际出口 IP 是否匹配。
5.2 SQL线程报错:1062主键冲突、中继日志损坏
如果Slave_IO_Running: Yes而Slave_SQL_Running: No,说明日志拉回来了但重放失败。最常见的错误码是 1062,主键冲突:
Last_SQL_Error: Could not execute Write_rows event on table mydb.t_user; Duplicate entry '1001' for key 't_user.PRIMARY'出现 1062 的根本原因是从库上已经有一条相同主键的数据,但主库又插入了一次,复制线程没法重复插入。这通常是因为:从库在复制启动之前被手动写过数据,或者初始数据备份时没有锁住主库导致数据不一致。
如果确认只是少数几条错误并且业务可以容忍跳过,可以执行:
STOP SLAVE; SET GLOBAL sql_slave_skip_counter = 1; START SLAVE;sql_slave_skip_counter = 1会让 SQL 线程跳过当前一条事务。注意是一次跳一个事务,不是跳过一行记录。如果错误很多,一条条跳不现实,还是老老实实重建从库更干净。
有时候 SQL 线程报的是中继日志损坏,比如relay log read failure。这种情况下不要硬修,直接清理从库复制状态重建:
STOP SLAVE; RESET SLAVE ALL;然后回到第 4 章,重新做 CHANGE MASTER TO 和 START SLAVE。如果是从库数据已经彻底乱了,就得重新导一次全量备份,回头重新走初始化流程。这确实是笨办法,但在实验环境里往往比重试更快。
5.3 从库被误写入导致复制中断的恢复
我印象最深的一次事故是,开发同学在从库上手动执行了一条 UPDATE,把某张表的状态字段改了。结果主库对应记录也更新了,复制线程重放时发现从库的值已经变掉了,不管怎么重放都对不上。
这种“人为写入从库”造成的数据冲突,没有完美的自动修复方案。最稳妥的办法是重建从库:在主库重新做一次一致性备份,传到从库恢复,从库清空复制关系再重新指向主库。整个过程在业务低峰期做,几分钟就能完成。
如果你确认冲突可以忽略,或者只是测试环境想快速让复制跑起来,再考虑用sql_slave_skip_counter跳过。但生产环境我一定不建议这么干,因为跳过之后从库数据和主库会存在隐藏的不一致,后面排查问题会更痛苦。
预防远比修复重要。read_only=1必须开在从库上,同时从库的业务账号绝对不能授予 SUPER 权限。普通账号只有 SELECT 权限更好,虽然业务需要读,但从库完全不需要写入口。
5.4 一键巡检主从状态的核心命令
我自己不管搭哪套主从,都会写一个简单的巡检脚本,核心就是这两条命令的检查:
mysql -uroot -pXXX -e "SHOW SLAVE STATUS\G" | grep -E 'Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_IO_Error|Last_SQL_Error'输出里两个 Running 是 Yes、延迟值不高,就说明复制在正常跑。如果拦截到 Last_IO_Error 或 Last_SQL_Error 非空,再用脚本自动发告警。
更进阶的巡检可以对比主从两边的事务位置:
SHOW MASTER STATUS;SHOW SLAVE STATUS;观察Master_Log_File和Read_Master_Log_Pos是否持续增长。如果Read_Master_Log_Pos长时间不增长,说明 IO 线程可能已经断了。
6. 主从复制跑起来之后的日常维护
6.1 从库的read_only、备份与延迟监控
从库不只是个冷备机,正确用法是承担备份和只读查询。read_only=1已经限制了普通账号写入,所以可以放心把报表查询、数据导出这些只读业务切到从库,减轻主库压力。
备份优先在从库上做,这样不会影响主库的读写性能。mysqldump 命令和前面主库备份基本一样,只是不加--master-data=2也行。如果是每天全量备份,用 cron 定时执行:
0 2 * * * /usr/local/mysql/bin/mysqldump -uroot -pXXX --single-transaction --routines --triggers --all-databases | gzip > /data/backup/mysql_$(date +\%F).sql.gz延迟监控主要看Seconds_Behind_Master。注意这个字段是 SQL 线程执行的时间差估算值,不能完全代表数据一致性,但作为日常告警足够。如果这个值持续上涨,常见原因有:从库机器性能比主库差、主库有大事务(一次 UPDATE 几十万行)、从库磁盘 IO 慢。
还有一种隐藏情况:主库有大量并行事务,从库默认是单线程重放,速度跟不上。MySQL 8.0 默认开启了多线程复制,replica_parallel_workers默认值是 4,一般不用动。如果延迟严重,可以调大一点,但要结合从库 CPU 核数谨慎调整。
6.2 binlog清理策略与磁盘空间
binlog 占的磁盘空间比想象中大,尤其 ROW 格式下一条 UPDATE 可能产生几 MB 日志。不清理的话/data分区早晚被填满,MySQL 会因为无法写 binlog 直接挂掉。
自动清理靠配置:
binlog-expire-log-seconds=6048007 天是合理值。如果你的从库经常连续宕机好几天,这个值可以再调大,比如 14 天,代价是磁盘占用翻倍。
手动清理可以用:
PURGE BINARY LOGS TO 'mysql-bin.000010';意思是清理到这个文件之前的日志。注意不要用RESET MASTER,那个会把所有 binlog 清光并重置,从库会彻底断掉复制。
清理 binlog 的同时要盯着主从延迟。理想情况是从库已经追平所有日志,再清理才安全。如果从库延迟还有几个 G 的日志没追完,主库这边 binlog 已经被清掉,从库就会报错The slave I/O thread stops because master and slave have equal MySQL server UUIDs或者 1236Could not find first log file name in binary log index,这时候只能重新全量恢复从库。
6.3 故障切换的基本思路
主库宕机后,把从库提升为主库是标准操作,但不要慌着手动点。基本步骤是这样的:
- 确认从库复制已经追平,
Seconds_Behind_Master为 0。如果没追平,数据会有丢失。 - 在从库执行
STOP SLAVE; RESET SLAVE ALL;,断开从库的复制关系。 - 如果从库配置了
read_only=1,改为读写:SET GLOBAL read_only=OFF; - 确认从库自身开了 binlog 且 server-id 全局唯一,这样它才能继续给新的从库提供日志。
- 修改应用连接串,把数据库 IP 切到原从库。
跑过一次切换之后你会体会到 GTID 的好处。如果最初用的 binlog 位置法,新主库提升后,原来的主库如果修复回来了,重新接入复制时还要再找一次 position;而 GTID 模式下,只要两边都开了 GTID,直接用MASTER_AUTO_POSITION=1就能重新对齐,基本不用人工干预。
关于故障切换,我的建议是实验环境必须演练一到两次,别等到真出故障了再查资料。重点演练两个环节:一是从库追平判定,二是应用连接切换。很多团队栽就栽在从库延迟没追平就把流量切过去了,数据丢了几千条,后面恢复起来非常麻烦。
这些步骤我在麒麟桌面V10上完整走过一遍,期间踩得最深的就是克隆虚拟机的 server-uuid 重复和防火墙没放行 3306。你如果按这篇的顺序操作,基本能绕开我踩过的坑。最后再提醒一句:生产环境做主从,务必先备份、再变更;测试环境出了问题,直接把从库重建一次往往比重试和修数据快得多。