
后端系统扛到一定体量数据库一定会成为你最睡不着觉的那台机器。很多团队的第一反应就是上主从复制主库负责写从库分担读再顺手做个灾备。但真到动手搭建的时候一堆细节会把你按在地上反复摩擦——server_id重复、UUID冲突、GTID和位点对不上、SSL握手失败、从库延迟、指定表同步不生效……这一章我们不谈抽象架构就从一台Rocky Linux 9开始把MySQL 8主从复制的完整链路从零搭起来再带到高可用场景里去看它还缺什么。不管你是后端开发、运维还是刚接手数据库集群的新人照着做一遍基本就能把这些坑认全。1. 为什么单机MySQL扛不住“高可用”这三个字1.1 主从复制到底解决了什么问题先泼一盆冷水主从复制不等于高可用但所有高可用方案的地基都是主从复制。单机MySQL哪怕配置再高、硬盘全固态、备份脚本写得再漂亮也逃不过三类事故硬件故障磁盘坏道、内存报错、电源老化机器说挂就挂系统级事故内核panic、文件系统损坏、机房断电人为事故手滑执行了DROP TABLE或者一条大事务把binlog写爆。主从复制解决的核心痛点是把数据在另一台机器上实时冗余一份。主库挂了从库还有完整数据读压力大了从库能分担查询备份也能直接在从库做不打扰线上主库。这三件事单靠一个MySQL实例都做不到。但要注意一个常见误解很多人以为主从复制搭好之后“高可用就完成了”。实际上主库宕机后从库不会自动顶上去复制链路也不会自动重连需要人工或额外组件去做故障切换。这一章先把复制本身做扎实后续章节再聊“从能同步到能切换”的最后一公里。1.2 常见拓扑选型一主一从、一主多从、双主、级联实际生产环境里最常见的四种拓扑各有适用场景拓扑特点典型用途风险点一主一从结构最简单成本低基础容灾、备份、轻度读写分离单点故障仍在从库侧主库挂了切换要人工一主多从多个从库分担读流量读写分离、多环境复制、报表查询从库延迟放大同步负担变大双主互为主从两边都能写配合VIP对外高可用切换、跨机房容灾写冲突要应用层规避配置复杂级联复制A→B→CB做中转数据归档、跨机房分发链路越长延迟越大排错链路变深新手入门直接搭一主一从就对了先把复制原理和排错手感练出来。双主和级联都是在这个基础之上的花样原理完全一样无非是多几份配置、多几条复制链路。2. 环境准备Rocky Linux 9上用rpm包安装MySQL 82.1 先解决系统自带的MySQL模块冲突Rocky Linux 9以及RHEL 9系有个非常坑的默认行为系统软件仓库里自带的mysql模块实际指向的是 MariaDB。如果你直接执行yum install mysql装出来的是MariaDB路径、版本、参数跟MySQL 8完全不是一回事。主从复制教程一旦在错误的地基上做后面所有配置都会对不上。所以第一步必须先把系统自带的模块禁用掉dnf module disable mysql -y执行完可以用dnf module list mysql确认状态应该显示为“禁用”。这一步很多教程不会提但我在新机器上踩过两次都是装到一半才发现mysqld变成了mariadbd特别浪费时间。2.2 安装与初始化的关键步骤然后从MySQL官方Yum仓库下载对应el9的release包。官方仓库页面会提供最新的rpm包名典型的是mysql80-community-release-el9-1.noarch.rpm。下载后先装release包再装服务端wget https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm rpm -ivh mysql80-community-release-el9-1.noarch.rpm dnf install mysql-community-server -y装完之后启动MySQLsystemctl start mysqld systemctl status mysqldMySQL 8在初始化完成后会自动生成一个临时root密码日志里能找到grep temporary password /var/log/mysqld.log拿到临时密码后登录立刻修改root密码mysql -uroot -p ALTER USER rootlocalhost IDENTIFIED BY 你的强密码;这里要注意MySQL 8默认装了validate_password组件密码要求包含大小写字母、数字和特殊符号纯数字或者太短的密码会被直接拒绝。这也是很多人刚装完就卡住的地方。2.3 防火墙、开机自启和连接方式主从复制要求主库的3306端口对从库可达所以防火墙要放行firewall-cmd --permanent --add-port3306/tcp firewall-cmd --reload设置开机自启systemctl enable mysqld然后本机测试连接。这里有个很基础的坑mysql -uroot -p默认走的是本地socket文件路径一般在/tmp/mysql.sock或/var/lib/mysql/mysql.sock。如果你拿到临时密码后执行登录报错ERROR 2002连不上socket多数情况是服务还没启动、或者socket路径被改过。可以先确认进程是否在跑ps -ef | grep mysqld ss -lntp | grep 3306想确认TCP能不能通可以强制走TCP协议连接mysql -h127.0.0.1 -P3306 -uroot -p到这里两台机器主库和从库都装好MySQL之后就可以进入正题了。3. 先理解复制链路binlog、relay log和三个线程3.1 一次INSERT在主从两端的完整旅程假设你在主库执行一条INSERT主从复制链路里发生的事是这样的主库把这条写入操作记录到binlog里然后返回客户端成功。在此之前一个叫做MYSQL_BIN_LOG的内部机制会把事务按提交顺序编号每个事务在binlog里都有一个文件偏移位点。从库这边有两个线程在干活。IO线程负责连接主库把主库binlog里新增的内容拉到本地原样写入从库自己的relay log中继日志。紧接着SQL线程读取relay log把里面的日志内容在从库上重放一遍。整个复制就这么简单——拉日志、放日志、读日志、执行日志。用个生活化类比主库是出版社binlog是每天的报纸大样从库的IO线程是发行员把大样拉到各地的印刷厂存起来SQL线程是印刷工照着一模一样的大样重新印一份同样的报纸。所以从库的数据严格来说不是“实时同步”而是“异步重放”中间存在一个不可忽略的延迟窗口。3.2 位点复制和GTID复制的差别传统的主从复制靠位点定位从库记录自己消费到了主库binlog的哪个文件、哪个偏移量然后在主库有新的日志时从这个位置继续拉。一旦从库落后太多或者主库binlog被purge清理掉位点就对不上了链路直接断掉而且重建很痛苦。MySQL 5.6开始引入GTID全局事务标识符每个事务有一个全局唯一ID格式是UUID:事务序号。GTID模式下的复制不再依赖文件路径和偏移量从库只要说“我执行到哪个GTID了”主库就知道接下来要补哪些事务天然支持断点续传也天然避免了重复执行的问题。MySQL 8.0默认已经开启GTID配置文件里gtid_modeON。新项目我强烈建议直接用GTID复制不要再回到位点模式那条老路。老手可能习惯看MASTER_LOG_FILE和MASTER_LOG_POS但GTID时代管理成本低得多出问题也好定位。3.3 为什么我强烈建议binlog_format用ROWbinlog有三种格式STATEMENT、ROW、MIXED。STATEMENT记录的是SQL语句本身优点是日志量小但遇到UUID()、NOW()这种非确定性函数或者存储过程、触发器里有随机逻辑时主从执行结果就可能不一致——这就是为什么很多主从数据对不齐的案例根源都在STATEMENT格式上。ROW记录的是每一行的实际变更值哪个库哪张表哪个主键哪几列变了都清清楚楚。虽然binlog体积会变大但主从数据一致性最有保障。MySQL 8.0默认就是ROW格式。MIXED是两者的混合看起来两边好处都占但实际排错时你会发现日志一会儿是SQL一会儿是行变更反而增加心智负担。我的建议就一条binlog_formatROW不要犹豫。磁盘贵还是数据不一致的代价贵答案不言自明。而且后面做表级同步、做闪回恢复、做数据订正ROW格式的支持都好得多。4. 手工搭建一主一从每一步配置背后的理由4.1 主库和从库的my.cnf配置假设两台机器都叫mysql-master和mysql-replicaIP分别为192.168.1.10和192.168.1.20。主库的/etc/my.cnf在[mysqld]段里这样配置[mysqld] server-id 1 log-bin mysql-bin binlog_format ROW gtid_mode ON enforce_gtid_consistency ON log_replica_updates ONserver-id是复制链路的身份标识主从必须不同重复了会直接导致复制失败log-bin开启binlog主库不开binlog就等于没有“大样”可以拉gtid_mode和enforce_gtid_consistency是GTID复制的开关log_replica_updates让从库在重放relay log时也写自己的binlog这样从库可以继续给下游复制用级联复制必须开。从库的配置[mysqld] server-id 2 relay_log mysql-relay-bin read_only ON gtid_mode ON enforce_gtid_consistency ON注意server-id 2不能和主库一样。read_only ON这行很多人忽略但非常重要从库在复制重放过程中不受read_only限制SQL线程照样能写数据但应用程序直接连从库写数据会被拒绝。这就防止了“应用误连从库写入”导致的复制中断和数据不一致。配置改完一定要重启MySQLsystemctl restart mysqld重启后先确认参数生效SHOW VARIABLES LIKE server_id; SHOW VARIABLES LIKE gtid_mode;4.2 创建复制账号与初始化从库数据主库上创建一个专门用于复制的账号不要用root直接做复制CREATE USER repl% IDENTIFIED BY Repl2024Strong; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl%; FLUSH PRIVILEGES;REPLICATION SLAVE权限只允许从库连接过来拉binlogREPLICATION CLIENT允许查看主库状态。不需要给这个账号任何业务库的读写权限最小权限原则。接下来是把主库已有数据搬到从库。生产环境最理想的是用物理备份工具如XtraBackup但为了可复现性我们用mysqldump逻辑备份。关键参数是--single-transaction保证一致性快照和--source-data2自动把binlog位点信息记录到备份文件里mysqldump -uroot -p --single-transaction --all-databases --source-data2 backup.sql如果你的MySQL版本还在5.7或更早参数名是--master-data2效果一样。把backup.sql传到从库然后恢复scp backup.sql root192.168.1.20:/tmp/ mysql -uroot -p /tmp/backup.sql恢复完之后备份文件里已经包含了GTID点位信息从库不需要再手工指定位点后面用SOURCE_AUTO_POSITION1会自动衔接。4.3 启动复制链路与验证同步从库上执行以下命令MySQL 8.0推荐的新语法CHANGE REPLICATION SOURCE TO SOURCE_HOST192.168.1.10, SOURCE_USERrepl, SOURCE_PASSWORDRepl2024Strong, SOURCE_PORT3306, SOURCE_AUTO_POSITION1;如果你习惯老语法CHANGE MASTER TO MASTER_HOST...在8.0依然兼容但这套新语法才是长期方向。然后启动复制线程START REPLICA;查看复制状态SHOW REPLICA STATUS\G重点看两个指标必须是YesReplica_IO_Running: Yes Replica_SQL_Running: YesReplica_IO_Running: Yes说明IO线程连上了主库并正常拉日志Replica_SQL_Running: Yes说明SQL线程在正常重放。如果其中一个是No结合Last_IO_Error或Last_SQL_Error定位后面章节专门讲排错。同步验证这一步不要省。在主库建一个测试库和表CREATE DATABASE test_sync; USE test_sync; CREATE TABLE t1 (id INT PRIMARY KEY, val VARCHAR(50)); INSERT INTO t1 VALUES (1, hello);然后去从库查USE test_sync; SELECT * FROM t1;能查到数据链路就通了。我习惯每搭好一组主从都做这个验证确认是库里真实同步而不只是看到两个线程Running就收工。4.4 启动复制的常见拦路虎第一次手工搭建时最容易遇到几个问题UUID重复如果你用虚拟机克隆的方式准备了两台机器MySQL数据目录下的auto.cnf里保存的server_uuid可能一模一样主库会拒绝从库连接。解决办法是删除从库的auto.cnf后重启rm -f /var/lib/mysql/auto.cnf systemctl restart mysqldserver_id重复两台机器默认都可能装着server_id1的配置启动复制时会报The server is not configured properly for replication。检查两边配置确保不同。账号host限制创建复制账号用repl%如果你图省事写成repllocalhost从库远程连不过来。主从数据初始不一致如果你跳过备份恢复直接从一台已经跑了很多业务、binlog早就被purge过的机器硬做复制中间断层根本补不回来。所以数据初始化这一步不要偷懒。5. 只同步远程库的某一张表表级复制的配置与坑5.1 replicate-do-table的写法我经常在社区里看到这种提问“远程库里有一张订单表我只想同步这一张到本地怎么搞”不少人直接在主从复制里把整库复制过去再从库删掉其他表这是本末倒置。MySQL从库原生支持表级过滤配置在从库的/etc/my.cnf里[mysqld] replicate-do-table mydb.orders这样从库只会复制mydb.orders这张表的变更其他表即使主库有从库也不会有数据。注意格式是库名.表名不能写成裸表名。如果要同步多张表就写多行replicate-do-table mydb.orders replicate-do-table mydb.customer还有一种更灵活的写法叫通配符过滤replicate-wild-do-table mydb.%这个匹配的是所有属于mydb库的表。当你需要同步整个库但不想要其他库时用这个比replicate-do-db更安全因为它对库名和表名都做精确匹配。配置改完一样要重启从库服务。重启后执行SHOW REPLICA STATUS在输出里能看到Replicate_Do_Table和Replicate_Wild_Do_Table两行确认过滤规则已生效。5.2 表级复制的边界与注意事项表级复制有一个非常隐蔽的坑如果源库在同步过程中执行了跨库操作比如在事务里更新了mydb.orders同时也更新了otherdb.pay_log从库会因为otherdb不在复制范围内而跳过整个事务导致mydb.orders的变更也没同步过来。这种情况下从库SQL线程会报错中断需要跳过错误才能继续。另一个坑是大小写。Linux上MySQL默认区分表名大小写lower_case_table_names0你配置里写的表名和主库实际表名必须完全一致。很多人从Windows迁移到Linux之前在Windows上不区分大小写的习惯到这里就翻车。再说一个ROW格式下的性能问题如果目标表没有主键ROW格式的binlog在从库重放时只能全表扫描定位行同步效率和延迟都非常难看。表级同步尤其要注意因为本来就只有一张表在同步别让它在从库变成全表扫描的重灾区。5.3 数据量大时的同步优化思路如果目标表数据量特别大比如几千万行、上亿行直接用mysqldump全量备份再恢复到从库会非常耗时而且中途新产生的binlog可能导致GTID上下文重放冲突。我的做法是用mysqldump --where把数据切成子集分批恢复mysqldump -uroot -p mydb orders --single-transaction --wherecreate_date 2024-01-01 orders_2024.sql如果业务上只需要最近的数据这招能大幅缩短初始化时间。还有一种场景是只需要远程表的数据做分析不要求严格实时。这时除了主从复制也可以考虑Federated引擎做跨实例查询本地建一个映射表查询时实时去远程拉但Federated的性能和稳定度都比不上真正的复制数据量小可以玩数据量大还是别折腾。6. 主从链路排查手记从报错日志到数据修复6.1 ERROR 2002socket路径对不上该怎么办ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock是安装和连接阶段出现频率极高的报错。字面意思就是客户端想在/tmp/mysql.sock这个位置找socket文件没找到。排查分三步确认mysqld进程是否真的在跑systemctl status mysqld没跑就先启动如果进程在跑还报错说明socket路径不是默认值去看/etc/my.cnf里[mysqld]段的socket指向哪里实在不想深究socket直接改用TCP连接绕过去mysql -h127.0.0.1 -P3306 -uroot -p加个-h强制走TCP就不依赖socket文件了。这套思路放在主从复制里也一样从库连接主库时用的就是SOURCE_HOSTIP走TCP不会碰到socket问题反而是本机管理操作频繁踩到这个报错。6.2 SSL连接错误的处理姿势MySQL 8.0默认开启SSL主从复制连接时默认也会尝试建立加密连接。如果两边证书配置不匹配或者从库在握手阶段对证书校验失败SHOW REPLICA STATUS里会看到类似SSL connection error: protocol version mismatch的报错。解决思路有两种。内网环境为了降低握手开销可以直接关闭复制链路的SSL要求在CHANGE REPLICATION SOURCE TO里加一行CHANGE REPLICATION SOURCE TO SOURCE_HOST192.168.1.10, SOURCE_USERrepl, SOURCE_PASSWORDRepl2024Strong, SOURCE_PORT3306, SOURCE_AUTO_POSITION1, SOURCE_SSL0;如果安全要求高必须走加密连接那确认两边证书、CA、hostname校验都配置正确并把SOURCE_SSL_VERIFY_SERVER_CERT0内网无独立域名时常用加上。我在实际项目中更推荐一个折中方案复制链路走内网独立VLAN主从之间开启SSL但不做严格证书校验业务连接则保持原有安全策略。复制链路上的SSL更多是防嗅探内网VLAN已经把这层风险降到很低了。6.3 复制中断的常规排查链路复制中断优先看三处信息SHOW REPLICA STATUS\G里的Last_IO_Error和Last_SQL_ErrorMySQL日志文件/var/log/mysqld.log主库binlog位置和从库当前执行位置的对比。最常见的致因是主从数据不一致。比如主库某条更新在从库对应的行不存在SQL线程重放时就报Cant find record in table。这时候先别急着跳过错误要分析是业务bug导致的还是之前手工改过从库数据。如果只是想止血让它继续跑可以跳过当前错误事务只适合非GTID模式且明确知道影响范围STOP REPLICA; SET GLOBAL sql_slave_skip_counter 1; START REPLICA;GTID模式下不建议用skip_counter会破坏GTID连续性。GTID复制中跳过事务的正确做法是用空事务占据一个GTIDSTOP REPLICA; SET GTID_NEXTaaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:事务号; BEGIN; COMMIT; SET GTID_NEXTAUTOMATIC; START REPLICA;但跳过错误只是止血根因不清的话下个错误马上还会来。如果从库已经和主库偏离太多比如漏了十几个事务最省心的方案是重新做一次数据初始化把那台从库直接“重做”成干净副本。这个方案在数据量可控时是最稳的别舍不得那点时间。7. 从“能同步”到“高可用”还差哪几步7.1 自动故障切换与VIP方案主从复制搭好只能保证数据有冗余。主库宕机了从库不会自己上位业务流量也不会自动切过去。要谈高可用必须有自动切换组件。常用方案有这几类Keepalived VIP 自动切换脚本主从之间漂移一个虚拟IP脚本检测主库存活挂了就执行STOP REPLICA、提升从库、VIP漂移。简单但切换脚本要自己写存在脑裂风险。MHA老牌经典方案自动检测主库故障选出数据最完整的从库补齐relay log后提升为新主。成熟稳定适合MySQL 5.7及以前。Orchestrator基于Raft做拓扑管理有Web界面支持复制关系可视化切换逻辑比脚本可靠很多是最值得研究的一款。MySQL InnoDB Cluster / Group Replication官方方案基于组复制多节点强一致能自动选主适合新项目直接采用但运维门槛和资源开销都偏高。方案成熟度自动切换运维复杂度适用场景Keepalived脚本中可以但脆弱低小规模、能接受脚本兜底MHA高可靠中传统主从架构Orchestrator高可靠中高复杂拓扑、多从库InnoDB Cluster高原生高新项目、官方全家桶7.2 半同步复制把数据丢失窗口压到最小异步复制最大的隐患在于主库把事务提交成功返回客户端后binlog还没传到从库此时主库宕机那批事务就丢了。对写密集型业务来说这个丢失窗口哪怕只有几百毫秒也无法接受。半同步复制的逻辑是主库提交事务时必须等待至少一个从库确认已经收到并写入了relay log才向客户端返回成功。这样主库宕机时已提交的事务必定至少在一个从库上存在。MySQL 8.0里启用半同步复制需要装插件并在两边设置INSTALL PLUGIN rpl_semi_sync_source SONAME semisync_source.so; SET GLOBAL rpl_semi_sync_source_enabled 1;从库上执行INSTALL PLUGIN rpl_semi_sync_replica SONAME semisync_replica.so; SET GLOBAL rpl_semi_sync_replica_enabled 1;注意rpl_semi_sync_source_timeout这个参数默认值是1000毫秒。如果从库确认ACK超时主库会自动降级回异步模式避免因为从库出问题拖垮主库写入性能。这个降级机制要清楚它保证了可用性但也意味着极端情况下数据丢失风险会回升。7.3 读写分离与连接池的配合高可用的最后一个拼图是把从库的读能力真正用起来。应用层面改造接中间件是最常见的路径。ProxySQL、MySQL Router这类中间件负责把SQL请求路由到正确的实例写请求固定走主库读请求按权重分发到多个从库。中间件本身也要做高可用否则会变成新的单点。连接池在这里的角色容易被低估。应用频繁创建和销毁数据库连接开销极大从库多的时候如果不依赖连接池复用连接中间件到后端的连接数会被打爆。实际项目里我会关注两个数据连接池的活跃连接数、以及中间件后端的连接复用率。只要复用好从库再多也能稳得住。从库延迟是另一个必须监控的指标SHOW REPLICA STATUS里的Seconds_Behind_Master只能反映SQL线程比主库落后多少秒延迟一旦持续上涨就说明从库回放能力跟不上要么加从库拆流量要么优化大事务不能等业务投诉了才去看。我个人实际操作中的体会是主从复制这套地基打不牢后面接再多自动化、中间件、监控告警都白搭。与其在高可用工具链里卷来卷去不如先把复制原理、GTID机制、排错链路吃透——章节里的这些命令和排查思路都是线上真实场景里反复用到的。最后再分享一个小技巧每次搭建完主从别急着交付故意在主库执行一条会失败的SQL比如插入重复主键再观察从库的错误日志和状态变化。花十分钟做一次这种“故障预演”你对整个链路的掌控感会完全不同。