MySQL用了这么多年,真正让我意识到主从配置不是"加分项"而是"必选项"的,是一次线上事故——某天凌晨一个同事连错环境,一条UPDATE没带WHERE条件直接把用户表全量更新了。当时数据库只有一个实例,备份恢复花了将近两小时,业务直接瘫痪。那次之后,我就把主从复制列为所有线上MySQL实例的标配。这篇博客就围绕mysql的主从配置,把从原理到实操、再到运维踩坑的完整链路讲清楚,适合刚接手数据库运维的开发者,也适合准备搭建读写分离或高可用架构的团队参考。主从配置本质上解决的三个问题:读写分离(把查询压力分流到从库)、容灾(主库挂了之后还能从从库拉起数据)、数据备份窗口(可以在从库做备份而不影响线上)。如果你目前只有单机MySQL,业务量不大也不敏感,可能感受不到它的价值;但当CPU到峰值、磁盘IO打满、或者误操作需要找回数据时,你会庆幸当初多花了一个小时配好主从。1. 别等数据库挂了才想起主从:三种典型场景1.1 读写分离是主从最朴素的用途MySQL的单机性能瓶颈,绝大多数情况下不是CPU计算,而是磁盘IO和锁竞争。读多写少是互联网业务的常态,一点几万个请求里可能九成是SELECT。这些查询全部压到主库上,主库每次行锁、表锁、binlog刷盘都要跟一堆慢查询抢资源。搭建从库之后,把报表查询、数据分析、后台管理类的读操作全部路由到从库,主库的压力能降一个数量级。但读写分离有个隐藏前提:允许短暂的数据延迟。因为主从复制是异步的,主库提交事务之后,binlog传到从库并回放需要时间。如果你做一个"写完立刻读自己刚写的数据"的功能,必须强制走主库,或者接受在极端情况下读到旧数据。很多团队第一次做读写分离时没意识到这一点,上线后发现用户刚发完评论列表里看不到,一片吐槽。解决方案也很简单:ORM层按需配置,写操作和强一致性读走主库,弱一致性读走从库。1.2 高可用切换离不开"多副本"这个前提单点故障是数据库架构里最不能碰的底线。物理机宕机、断电、磁盘损坏,任何一个问题发生,没有副本就意味着从备份恢复到可用状态的时间以小时计算。有了从库之后,主库发生灾难时可以把业务流量切到从库,恢复时间从小时级降到分钟级。在主从架构上做高可用,需要在从库之上叠加一套切换机制。比如常见的主库故障自动切换方案,核心动作就是检测主库心跳丢失后,提升从库为新的主库。这里最关键的细节是:从库数据必须尽量追平主库。如果用异步复制且主库故障前binlog还没来得及传到从库,切换后就会丢失最后一段时间的数据。这也是半同步复制和组复制存在的意义,后面我会专门讲。1.3 日常备份和离线分析也可以挪到从库很多团队的备份策略是每天凌晨在服务器上mysqldump一次全量数据。如果直接在唯一的主库上跑备份,mysqldump对线上性能的影响非常明显——大量物理读和临时表操作会把主库IO吃满。把备份任务放到从库,主库的正常业务完全不受影响。同样,数据仓库抽取、报表统计、慢查询分析这些重IO任务,也适合放到从库。从库即使被这些任务拖垮,也不会拖累线上写入。我在实际运维中甚至会把从库单独部署在配置较低的机器上,专门承接这类"脏活累活",主库则保持在高端硬件上专注服务在线业务。提示:做这类规划时,要注意从库磁盘空间通常需要比主库更大,因为从库要承接备份文件和临时表空间。2. 主从复制跑得通的底层逻辑:binlog与三线程接力2.1 binlog:复制的数据源理解主从复制,最核心的是理解binlog(二进制日志)。MySQL在主库上记录所有引起数据变更的语句和事务信息到binlog,从库就是靠读取并重放这份日志来保持数据一致的。binlog有三种格式:STATEMENT、ROW、MIXED。STATEMENT记的是原始SQL,日志量小,但某些函数和不确定性操作在从库回放时会计算出不同结果;ROW记录的是每行数据的具体变更前后值,最安全,日志量大;MIXED是两者的混合。我强烈建议使用ROW格式,尤其是在从库上做数据恢复时,ROW格式能精确知道哪些行被改了。MySQL 8.0默认就是ROW,如果你还在用5.7且没改过,检查一下,很多默认配置还是STATEMENT。还有一个参数容易被忽略:binlog_row_image。它控制ROW格式下记录的字段范围,默认FULL(记录所有字段)。如果你为了省日志量把它改成MINIMAL,只记录被修改的字段,从库重放时会少很多判断依据,某些基于binlog做数据回滚的工具(比如解析binlog生成反向SQL的工具)也会失效。省那点日志量不值当,保持FULL。2.2 IO线程、SQL线程和Relay Log的中转站从库上有两个核心线程在后台工作。IO线程负责连接主库,把主库binlog的内容拉到从库,写入从本地的relay log(中继日志);SQL线程负责读取relay log,并在从库上按顺序重放,完成数据更新。在MySQL 5.6之前,SQL线程只有一个,这意味着从库回放是单线程的。主库是多线程并发写入,从库却只能一个一个串行回放,这就是从库延迟的根本原因。5.6之后引入了多线程复制(slave_parallel_workers),可以把一个库内的事务按不同worker并行回放。但要注意,并行回放不能随便开——必须基于相同数据库实例级别的组提交,数据库调度复杂。生产环境如果延迟不严重,先设置4个并发线程观察效果,不要盲目加到几十个。从MySQL 5.5开始,复制链路中还加入了半同步复制机制。异步复制下,主库提交事务时只要本地成功了就返回客户端成功,不管从库有没有收到;半同步复制则要求主库等待至少一个从库确认收到binlog后才提交返回。这个机制显著缩小了主库故障时可能丢失数据的时间窗口。3. 搭建前必看:参数取舍与版本选型3.1 版本与引擎选型,避免从第一步就埋雷建议主库和从库使用完全相同的MySQL大版本,最好是同一个小版本。为什么强调版本一致性?我见过一个案例:主库是5.7,从库是8.0,配置好之后复制链路经常莫名中断,原因是8.0对binlog默认的校验规则发生了变化,从库拉取5.7的binlog解析失败。还有一些旧版本生成的sql_mode在回放时会造成兼容性问题。复制协议虽然尽力向前兼容,但跨大版本的坑太多,不值得在生产环境冒险。存储引擎方面,线上业务表统一使用InnoDB。MyISAM不支持事务,且复制时对DML和DDL的处理跟InnoDB的行为有差异,主从切换后容易出数据不一致的问题。如果库里还残留MyISAM表,在配置主从之前用一条命令统一检查并转换:SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE ENGINE <> 'InnoDB';3.2 主从基础参数:每个参数都不是随便写的主库端核心参数:参数推荐值含义server-id全局唯一数字每个实例必须不同,复制链路靠它区分log-binmysql-bin开启binlog,文件名前缀binlog_formatROW行级复制,保证一致性expire_logs_days7~14自动清理过期binlog,注意磁盘占用max_binlog_size256M~1G单个binlog文件上限从库端核心参数:参数推荐值含义server-id全局唯一数字不能和主库及其他从库重复relay-logmysql-relay-bin中继日志前缀read_only1开启后从库只允许主库复制写入relay_log_recovery1崩溃后自动丢弃未完成的relay log并重新拉取skip_slave_start0实例重启后自动继续复制server-id要单独强调。很多初学者配置主从失败,排查半天才发现两台机器的server-id都是默认的1,复制链路根本建立不起来。另外read_only只对普通账号生效,拥有SUPER权限的账号依然能写从库,所以就算开着read_only,也不要到处乱用管理员账号操作从库。max_binlog_size如果配得太小,binlog文件数量会很多,从库IO线程要频繁请求新文件,且SHOW MASTER STATUS返回的文件号和位置会经常变化,定位复制位点时会有点烦。也可以在主库设置binlog_group_commit_sync_delay等参数优化组提交效率,但这两个参数影响的是复制延时的源头,属于进阶调优项,新手可以先不管。注意:修改server-id和log-bin等参数需要重启MySQL实例才能生效,建议在业务低峰期操作,并提前评估重启影响。4. 从零到一:主从配置完整实操4.1 主库端设置:账号权限与当前位点我自己搭建主从的顺序是固定的:先改主库配置、建复制账号、锁表查位点;再改从库配置、恢复数据、配置复制串。每个步骤做完都先验证一步,避免最后一起排错无从下手。第一步,在主库配置文件/etc/my.cnf的[mysqld]段加上前面表格里的主库参数,然后重启MySQL。检查binlog是否真的开启了:SHOW VARIABLES LIKE 'log_bin'; -- 预期: log_bin = ON SHOW MASTER STATUS\G -- 预期能返回文件号和位置,例如 File: mysql-bin.000012, Position: 3421第二步,创建专用的复制账号。复制账号权限只需要REPLICATION SLAVE,不要给all privileges,安全原则是最小权限:CREATE USER 'repl'@'192.168.10.%' IDENTIFIED BY 'StrongPassword_2024'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.10.%'; FLUSH PRIVILEGES;账号host按实际网段限制,除非跨网段情况,否则不要用%放开所有来源。复制密码不要用简单密码,因为它会明文出现在从库的CHANGE MASTER TO语句和连接日志中。第三步,一致性问题。主库如果持续有写入,直接备份拿到的数据集和记录的binlog位置是对不上的。标准做法是先用FLUSH TABLES WITH READ LOCK把主库的写入锁住:FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; -- 记下输出里的 File 和 Position然后在另一个终端做物理备份或mysqldump。备份完成后执行UNLOCK TABLES释放主库锁。注意读锁期间不要拖太长,mysqldump全量备份如果是上TB的大库,这个方案就不实际,需要改用Percona XtraBackup在线物理备份,它可以在不锁库的情况下做一致性快照,并自动读取binlog位点。4.2 从库初始化:宁可慢,不要快从库先装好相同的MySQL版本和配置参数,注意把server-id改成与主库不同的值。然后搭建复制链路有两种主流思路。思路一:冷备份迁移。把主库全库数据用mysqldump导出再导入从库。适合数据量不大的场景(几十GB以内),操作直观:mysqldump -uroot -p --single-transaction --master-data=2 --all-databases > backup.sql--master-data=2会在dump文件的注释位置自动写入主库当时的binlog文件和位置,这比手动记录方便得多。--single-transaction通过InnoDB的MVCC机制保证导出过程中读到的是一致快照,不会锁住业务写入。思路二:物理备份迁移。用xtrabackup --backup备份主库数据目录,再xtrabackup --prepare应用到从库对应目录。速度快,适合大库,但是操作步骤更复杂,而且要求主从的版本完全一致。对新手来说,数据量不大就先走mysqldump路线,把流程跑通了再说。4.3 从库执行接入指令:CHANGE MASTER TO的细节数据同步到从库之后,登录从库MySQL执行接入命令。这里如果用了mysqldump的--master-data=2,可以直接查看dump文件头部注释定位位点:CHANGE MASTER TO MASTER_HOST = '192.168.10.11', MASTER_USER = 'repl', MASTER_PASSWORD = 'StrongPassword_2024', MASTER_LOG_FILE = 'mysql-bin.000012', MASTER_LOG_POS = 3421;这里的MASTER_LOG_FILE和MASTER_LOG_POS必须与备份那一刻的SHOW MASTER STATUS完全对应。位置填错一个字节都不行,填大了从库会跳过部分事务导致数据缺失,填小了则会重复执行一堆已经执行过的binlog,大概率直接报主键冲突。执行后启动复制并检查状态:START SLAVE; SHOW SLAVE STATUS\G最需要关注的四行输出:Slave_IO_Running: Yes:IO线程连接主库正常Slave_SQL_Running: Yes:SQL线程回放正常Seconds_Behind_Master: 0:复制延迟为0Last_Errno/Last_Error:非0则报错原因在这里从MySQL 8.0.22开始,START SLAVE改成了START REPLICA,SHOW SLAVE STATUS改成了SHOW REPLICA STATUS,老命令依然兼容可用但会在日志里提示弃用。4.4 验证复制链路:写一条数据看效果我每次搭建完成都会做一个小实验来验证整个链路是真的通了,而不只是看着SHOW SLAVE STATUS的输出都是Yes。在主库建一个临时库,写几条数据,然后到从库查:-- 主库执行 CREATE DATABASE repl_test; USE repl_test; CREATE TABLE t1 (id INT PRIMARY KEY, val VARCHAR(100)); INSERT INTO t1 VALUES (1, 'hello'), (2, 'world');等一两秒,在从库执行:USE repl_test; SELECT * FROM t1;能查到这两条数据就说明链路通了。测完把临时库DROP掉,复制会自动同步删除。之所以要做这个验证,是因为IO和SQL线程都显示Yes的情况偶尔也会是假象:比如binlog位点填的是旧的,SQL线程已经把所有relay log回放完毕,但主库新写的binlog根本没被拉过来,这种情况下状态虽正常,数据却不同步。5. 运维中必然踩的坑:复制中断的排查思路5.1 IO线程断开的排查链路Slave_IO_Running一旦显示为Connecting或No,说明从库拉取binlog的连接出了问题。排查顺序我建议固定为:网络连通性 -> 复制账号权限 -> 主库binlog是否存在。网络问题最常见也最常被忽略。从库服务器先手动连一次主库的3306端口:telnet 192.168.10.11 3306如果通不了,检查防火墙和安全组策略。很多团队数据库服务器走内网IP,配置时用内网IP,但防火墙放行的却是业务网段,导致复制连接被随机丢弃。网络没问题再看账号权限。主库执行:SELECT User, Host FROM mysql.user WHERE User = 'repl';确认账号存在且Host配置正确。然后用从库上的MySQL客户端主动用复制账号连一次主库,验证密码是否正确:mysql -h 192.168.10.11 -u repl -p账号验证通过后,看主库binlog是否还在。如果主库设置了很短的expire_logs_days,而从库断连时间又超过了这个天数,从库请求的binlog文件已经被主库清理,IO线程会报Got fatal error 1236之类的错误,这种情况需要重新全量初始化一次从库,因为缺失的binlog已经无法补救了。5.2 SQL线程报错的恢复与跳过Slave_SQL_Running变No,基本是SQL线程回放binlog时遇到了错误。最常见的两种错误码:1062 Duplicate entry:重复插入。典型的位点重复,比如CHANGE MASTER TO的位点偏前,或之前曾跳过复制后又重新接回。1032 Can't find record:更新或删除的行不存在。典型的从库数据与主库不一致,比如有人违反read_only往从库写了数据。遇到这类错误,第一步是看SHOW SLAVE STATUS\G的Last_Error字段,确认是哪个库哪个表的哪条语句出错。如果是确凿的误操作数据不一致,可以选择跳过该事务继续复制:STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1; START SLAVE;SQL_SLAVE_SKIP_COUNTER=1表示跳过下一个错误事务。这个操作一定要慎用——跳过之后,从库会永久缺失这一条数据,后续如果该数据被其他操作引用,还会引发更大的不一致。我的原则是:跳过之前必须确认这条错误是"脏数据"还是"正常数据"。脏数据(误写入从库的数据导致的冲突)可以跳过;正常业务数据必须停下修复。SQL线程错误后,relay log里那些已经拉下来但还没执行的事务会堆积在本地。如果错误一直不处理,磁盘空间会被relay log占满。处理完成后通常建议执行一次RESET SLAVE或者让SQL线程自动清空已处理的relay log,避免磁盘告警。提示:如果主从数据不一致已经很严重,最稳妥的方式不是一点点修,而是重新做一次全量数据同步并重配复制。省事但绝对安全的方案,没有之一。5.3 延迟治理:复制不能无限追不上主库Seconds_Behind_Master长期大于几十秒,就属于需要介入的延迟了。延迟的来源按优先级排查:首先看主库是不是有大批量写操作。一次UPDATE几十万行的任务执行5分钟,这5分钟里从库SQL线程在追赶一个巨大的binlog事务,期间从库查到的都是旧数据。这种大事务不只是产生延迟,还会让主库的binlog文件瞬间膨胀,从库relay log同样暴增。生产建议:大批量语句拆成小批次执行,控制单事务影响行数。其次看从库服务器负载。SQL线程是单线程回放历史的时代已经过去了,但如果并发参数没调对,从库依然只有一个线程干活。检查:SHOW VARIABLES LIKE 'slave_parallel_workers';如果值是0,把它改成4~8之间。注意该参数的改动只需重启复制线程或者重启从库实例,不需要重新初始化数据。加大并发后,原来一直延迟很久赶不上的从库,通常能在几分钟内追平。最后看从库磁盘IO。如果从库同时还在跑备份任务,磁盘带宽被占用,复制回放的效率会大幅下降。把备份任务调度到复制延迟较小的时段,或者给备份任务做IO限速,都能改善。6. 进阶:GTID与半同步让主从更省心6.1 为什么推荐GTID:从"位点追杀"里解脱传统主从配置用MASTER_LOG_FILE和MASTER_LOG_POS定位复制起点,这在切换和重建从库时非常痛苦。比如从库A宕机,你新加一台从库B,想让它直接从主库拉数据,就必须手动找到主库当前binlog位点填入CHANGE MASTER TO,稍有偏差复制就出问题。GTID(Global Transaction Identifier)从根本上解决了这个问题。每个事务在主库执行时会被分配一个全局唯一的ID,从库通过MASTER_AUTO_POSITION = 1告诉主库"我已经执行到哪些事务了,把后面的事务发给我"。你完全不需要关注binlog文件名和位置,只要配置自动定位。启用GTID需要主从库配置文件中加入:gtid_mode = ON enforce_gtid_consistency = ON然后从库CHANGE MASTER使用:CHANGE MASTER TO MASTER_HOST = '192.168.10.11', MASTER_USER = 'repl', MASTER_PASSWORD = 'StrongPassword_2024', MASTER_AUTO_POSITION = 1; START SLAVE;GTID模式下的主从切换也很直观:如果主库故障要把从库提升为主库,直接执行STOP SLAVE; RESET MASTER;再开放写权限就够了,新主库的GTID集合自然包含旧主库已经执行的全部事务,不会有遗漏。6.2 半同步复制:牺牲一点性能,换来数据不丢纯异步复制有一个天然的窗口:主库提交事务并把binlog发给从库,这期间如果主库宕机,从库没收到这些事务,切换到从库就会丢数据。虽然这个窗口通常只有几毫秒,但对于资金类系统、订单系统来说,任何数据丢失都不能接受。半同步复制(semi-sync replication)通过插件方式实现:主库在事务提交时,必须等待至少一个从库确认收到binlog后才向客户端返回成功。这样主库返回成功时,该事务的binlog必然已经存在于至少一台从库上。如果从库始终不确认,超过超时时间(rpl_semi_sync_master_timeout)后会降级为异步复制,避免从库故障拖死主库。这个退化机制要理解清楚,半同步不是100%主从无延迟,而是尽量缩小丢失窗口。启用半同步前,确认插件文件存在(MySQL 5.7和8.0都内置了semisync插件)。配置:plugin_load_add = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so" rpl_semi_sync_master_enabled = 1 rpl_semi_sync_slave_enabled = 1 rpl_semi_sync_master_timeout = 3000配置完成后重启主从实例,从库还要再STOP SLAVE; START SLAVE;一次,让复制线程重新加载半同步状态。通过SHOW STATUS LIKE 'Rpl_semi_sync_master_status'可确认是否已开启。半同步复制的代价是每次事务提交都要多一次网络往返等待,写入性能会有一定下降,通常可以控制在5%~10%以内。如果业务对写入延迟极其敏感,可以只在核心库或者跨机房的从库上开启,不必全局铺开。7. 最后聊聊我踩过的几个细节坑主从配置本身不复杂,但生产环境的复杂往往来自细节。第一个坑是binlog格式不统一。主库如果之前是MIXED格式,后来只改了ROW格式,但没有把已经执行过的不兼容事务处理掉,从库可能会在某个老事务上报错。改格式最好配合全量重建从库,而不是在一个已有复制链路上直接改。第二个坑是从库提升为主库的角色转换。把从库提升为主库后,如果原主库恢复,又直接把它接回复制链路,会发生"主主冲突"——两台都能写,数据互为副本,很快出现主键冲突。正确处理是原主库降级为从库,并且做好read_only管控,禁止出现双写。第三个坑是复制监控。SHOW SLAVE STATUS\G不会自动告诉你故障,必须有监控报警兜底。最简单的方案是写定时任务,每分钟检查一次状态,检测到IO线程或SQL线程不是Yes,立即告警。主从故障后拖得越久,修复成本越高,最后可能只能重新全量同步数据。做mysql主从配置这件事,我个人的体会是:不要把它当成一次性的搭建任务,而要当成一个持续的运维习惯。从配置完成那一刻起,定期检查延迟、检查binlog保留时长、演练从库提升流程,这些才真正决定主从架构能不能在危机时刻扛住。如果你刚开始接触,照着本文的步骤把一套测试环境搭起来、再故意制造几个故障练手排错,一次完整的实操胜过十遍文档阅读。
MySQL用了这么多年,真正让我意识到主从配置不是"加分项"而是"必选项"的,是一次线上事故——某天凌晨一个同事连错环境,一条UPDATE没带WHERE条件直接把用户表全量更新了。当时数据库只有一个实例,备份恢复花了将近两小时,业务直接瘫痪。那次之后,我就把主从复制列为所有线上MySQL实例的标配。这篇博客就围绕mysql的主从配置,把从原理到实操、再到运维踩坑的完整链路讲清楚,适合刚接手数据库运维的开发者,也适合准备搭建读写分离或高可用架构的团队参考。主从配置本质上解决的三个问题:读写分离(把查询压力分流到从库)、容灾(主库挂了之后还能从从库拉起数据)、数据备份窗口(可以在从库做备份而不影响线上)。如果你目前只有单机MySQL,业务量不大也不敏感,可能感受不到它的价值;但当CPU到峰值、磁盘IO打满、或者误操作需要找回数据时,你会庆幸当初多花了一个小时配好主从。1. 别等数据库挂了才想起主从:三种典型场景1.1 读写分离是主从最朴素的用途MySQL的单机性能瓶颈,绝大多数情况下不是CPU计算,而是磁盘IO和锁竞争。读多写少是互联网业务的常态,一点几万个请求里可能九成是SELECT。这些查询全部压到主库上,主库每次行锁、表锁、binlog刷盘都要跟一堆慢查询抢资源。搭建从库之后,把报表查询、数据分析、后台管理类的读操作全部路由到从库,主库的压力能降一个数量级。但读写分离有个隐藏前提:允许短暂的数据延迟。因为主从复制是异步的,主库提交事务之后,binlog传到从库并回放需要时间。如果你做一个"写完立刻读自己刚写的数据"的功能,必须强制走主库,或者接受在极端情况下读到旧数据。很多团队第一次做读写分离时没意识到这一点,上线后发现用户刚发完评论列表里看不到,一片吐槽。解决方案也很简单:ORM层按需配置,写操作和强一致性读走主库,弱一致性读走从库。1.2 高可用切换离不开"多副本"这个前提单点故障是数据库架构里最不能碰的底线。物理机宕机、断电、磁盘损坏,任何一个问题发生,没有副本就意味着从备份恢复到可用状态的时间以小时计算。有了从库之后,主库发生灾难时可以把业务流量切到从库,恢复时间从小时级降到分钟级。在主从架构上做高可用,需要在从库之上叠加一套切换机制。比如常见的主库故障自动切换方案,核心动作就是检测主库心跳丢失后,提升从库为新的主库。这里最关键的细节是:从库数据必须尽量追平主库。如果用异步复制且主库故障前binlog还没来得及传到从库,切换后就会丢失最后一段时间的数据。这也是半同步复制和组复制存在的意义,后面我会专门讲。1.3 日常备份和离线分析也可以挪到从库很多团队的备份策略是每天凌晨在服务器上mysqldump一次全量数据。如果直接在唯一的主库上跑备份,mysqldump对线上性能的影响非常明显——大量物理读和临时表操作会把主库IO吃满。把备份任务放到从库,主库的正常业务完全不受影响。同样,数据仓库抽取、报表统计、慢查询分析这些重IO任务,也适合放到从库。从库即使被这些任务拖垮,也不会拖累线上写入。我在实际运维中甚至会把从库单独部署在配置较低的机器上,专门承接这类"脏活累活",主库则保持在高端硬件上专注服务在线业务。提示:做这类规划时,要注意从库磁盘空间通常需要比主库更大,因为从库要承接备份文件和临时表空间。2. 主从复制跑得通的底层逻辑:binlog与三线程接力2.1 binlog:复制的数据源理解主从复制,最核心的是理解binlog(二进制日志)。MySQL在主库上记录所有引起数据变更的语句和事务信息到binlog,从库就是靠读取并重放这份日志来保持数据一致的。binlog有三种格式:STATEMENT、ROW、MIXED。STATEMENT记的是原始SQL,日志量小,但某些函数和不确定性操作在从库回放时会计算出不同结果;ROW记录的是每行数据的具体变更前后值,最安全,日志量大;MIXED是两者的混合。我强烈建议使用ROW格式,尤其是在从库上做数据恢复时,ROW格式能精确知道哪些行被改了。MySQL 8.0默认就是ROW,如果你还在用5.7且没改过,检查一下,很多默认配置还是STATEMENT。还有一个参数容易被忽略:binlog_row_image。它控制ROW格式下记录的字段范围,默认FULL(记录所有字段)。如果你为了省日志量把它改成MINIMAL,只记录被修改的字段,从库重放时会少很多判断依据,某些基于binlog做数据回滚的工具(比如解析binlog生成反向SQL的工具)也会失效。省那点日志量不值当,保持FULL。2.2 IO线程、SQL线程和Relay Log的中转站从库上有两个核心线程在后台工作。IO线程负责连接主库,把主库binlog的内容拉到从库,写入从本地的relay log(中继日志);SQL线程负责读取relay log,并在从库上按顺序重放,完成数据更新。在MySQL 5.6之前,SQL线程只有一个,这意味着从库回放是单线程的。主库是多线程并发写入,从库却只能一个一个串行回放,这就是从库延迟的根本原因。5.6之后引入了多线程复制(slave_parallel_workers),可以把一个库内的事务按不同worker并行回放。但要注意,并行回放不能随便开——必须基于相同数据库实例级别的组提交,数据库调度复杂。生产环境如果延迟不严重,先设置4个并发线程观察效果,不要盲目加到几十个。从MySQL 5.5开始,复制链路中还加入了半同步复制机制。异步复制下,主库提交事务时只要本地成功了就返回客户端成功,不管从库有没有收到;半同步复制则要求主库等待至少一个从库确认收到binlog后才提交返回。这个机制显著缩小了主库故障时可能丢失数据的时间窗口。3. 搭建前必看:参数取舍与版本选型3.1 版本与引擎选型,避免从第一步就埋雷建议主库和从库使用完全相同的MySQL大版本,最好是同一个小版本。为什么强调版本一致性?我见过一个案例:主库是5.7,从库是8.0,配置好之后复制链路经常莫名中断,原因是8.0对binlog默认的校验规则发生了变化,从库拉取5.7的binlog解析失败。还有一些旧版本生成的sql_mode在回放时会造成兼容性问题。复制协议虽然尽力向前兼容,但跨大版本的坑太多,不值得在生产环境冒险。存储引擎方面,线上业务表统一使用InnoDB。MyISAM不支持事务,且复制时对DML和DDL的处理跟InnoDB的行为有差异,主从切换后容易出数据不一致的问题。如果库里还残留MyISAM表,在配置主从之前用一条命令统一检查并转换:SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE ENGINE <> 'InnoDB';3.2 主从基础参数:每个参数都不是随便写的主库端核心参数:参数推荐值含义server-id全局唯一数字每个实例必须不同,复制链路靠它区分log-binmysql-bin开启binlog,文件名前缀binlog_formatROW行级复制,保证一致性expire_logs_days7~14自动清理过期binlog,注意磁盘占用max_binlog_size256M~1G单个binlog文件上限从库端核心参数:参数推荐值含义server-id全局唯一数字不能和主库及其他从库重复relay-logmysql-relay-bin中继日志前缀read_only1开启后从库只允许主库复制写入relay_log_recovery1崩溃后自动丢弃未完成的relay log并重新拉取skip_slave_start0实例重启后自动继续复制server-id要单独强调。很多初学者配置主从失败,排查半天才发现两台机器的server-id都是默认的1,复制链路根本建立不起来。另外read_only只对普通账号生效,拥有SUPER权限的账号依然能写从库,所以就算开着read_only,也不要到处乱用管理员账号操作从库。max_binlog_size如果配得太小,binlog文件数量会很多,从库IO线程要频繁请求新文件,且SHOW MASTER STATUS返回的文件号和位置会经常变化,定位复制位点时会有点烦。也可以在主库设置binlog_group_commit_sync_delay等参数优化组提交效率,但这两个参数影响的是复制延时的源头,属于进阶调优项,新手可以先不管。注意:修改server-id和log-bin等参数需要重启MySQL实例才能生效,建议在业务低峰期操作,并提前评估重启影响。4. 从零到一:主从配置完整实操4.1 主库端设置:账号权限与当前位点我自己搭建主从的顺序是固定的:先改主库配置、建复制账号、锁表查位点;再改从库配置、恢复数据、配置复制串。每个步骤做完都先验证一步,避免最后一起排错无从下手。第一步,在主库配置文件/etc/my.cnf的[mysqld]段加上前面表格里的主库参数,然后重启MySQL。检查binlog是否真的开启了:SHOW VARIABLES LIKE 'log_bin'; -- 预期: log_bin = ON SHOW MASTER STATUS\G -- 预期能返回文件号和位置,例如 File: mysql-bin.000012, Position: 3421第二步,创建专用的复制账号。复制账号权限只需要REPLICATION SLAVE,不要给all privileges,安全原则是最小权限:CREATE USER 'repl'@'192.168.10.%' IDENTIFIED BY 'StrongPassword_2024'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.10.%'; FLUSH PRIVILEGES;账号host按实际网段限制,除非跨网段情况,否则不要用%放开所有来源。复制密码不要用简单密码,因为它会明文出现在从库的CHANGE MASTER TO语句和连接日志中。第三步,一致性问题。主库如果持续有写入,直接备份拿到的数据集和记录的binlog位置是对不上的。标准做法是先用FLUSH TABLES WITH READ LOCK把主库的写入锁住:FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; -- 记下输出里的 File 和 Position然后在另一个终端做物理备份或mysqldump。备份完成后执行UNLOCK TABLES释放主库锁。注意读锁期间不要拖太长,mysqldump全量备份如果是上TB的大库,这个方案就不实际,需要改用Percona XtraBackup在线物理备份,它可以在不锁库的情况下做一致性快照,并自动读取binlog位点。4.2 从库初始化:宁可慢,不要快从库先装好相同的MySQL版本和配置参数,注意把server-id改成与主库不同的值。然后搭建复制链路有两种主流思路。思路一:冷备份迁移。把主库全库数据用mysqldump导出再导入从库。适合数据量不大的场景(几十GB以内),操作直观:mysqldump -uroot -p --single-transaction --master-data=2 --all-databases > backup.sql--master-data=2会在dump文件的注释位置自动写入主库当时的binlog文件和位置,这比手动记录方便得多。--single-transaction通过InnoDB的MVCC机制保证导出过程中读到的是一致快照,不会锁住业务写入。思路二:物理备份迁移。用xtrabackup --backup备份主库数据目录,再xtrabackup --prepare应用到从库对应目录。速度快,适合大库,但是操作步骤更复杂,而且要求主从的版本完全一致。对新手来说,数据量不大就先走mysqldump路线,把流程跑通了再说。4.3 从库执行接入指令:CHANGE MASTER TO的细节数据同步到从库之后,登录从库MySQL执行接入命令。这里如果用了mysqldump的--master-data=2,可以直接查看dump文件头部注释定位位点:CHANGE MASTER TO MASTER_HOST = '192.168.10.11', MASTER_USER = 'repl', MASTER_PASSWORD = 'StrongPassword_2024', MASTER_LOG_FILE = 'mysql-bin.000012', MASTER_LOG_POS = 3421;这里的MASTER_LOG_FILE和MASTER_LOG_POS必须与备份那一刻的SHOW MASTER STATUS完全对应。位置填错一个字节都不行,填大了从库会跳过部分事务导致数据缺失,填小了则会重复执行一堆已经执行过的binlog,大概率直接报主键冲突。执行后启动复制并检查状态:START SLAVE; SHOW SLAVE STATUS\G最需要关注的四行输出:Slave_IO_Running: Yes:IO线程连接主库正常Slave_SQL_Running: Yes:SQL线程回放正常Seconds_Behind_Master: 0:复制延迟为0Last_Errno/Last_Error:非0则报错原因在这里从MySQL 8.0.22开始,START SLAVE改成了START REPLICA,SHOW SLAVE STATUS改成了SHOW REPLICA STATUS,老命令依然兼容可用但会在日志里提示弃用。4.4 验证复制链路:写一条数据看效果我每次搭建完成都会做一个小实验来验证整个链路是真的通了,而不只是看着SHOW SLAVE STATUS的输出都是Yes。在主库建一个临时库,写几条数据,然后到从库查:-- 主库执行 CREATE DATABASE repl_test; USE repl_test; CREATE TABLE t1 (id INT PRIMARY KEY, val VARCHAR(100)); INSERT INTO t1 VALUES (1, 'hello'), (2, 'world');等一两秒,在从库执行:USE repl_test; SELECT * FROM t1;能查到这两条数据就说明链路通了。测完把临时库DROP掉,复制会自动同步删除。之所以要做这个验证,是因为IO和SQL线程都显示Yes的情况偶尔也会是假象:比如binlog位点填的是旧的,SQL线程已经把所有relay log回放完毕,但主库新写的binlog根本没被拉过来,这种情况下状态虽正常,数据却不同步。5. 运维中必然踩的坑:复制中断的排查思路5.1 IO线程断开的排查链路Slave_IO_Running一旦显示为Connecting或No,说明从库拉取binlog的连接出了问题。排查顺序我建议固定为:网络连通性 -> 复制账号权限 -> 主库binlog是否存在。网络问题最常见也最常被忽略。从库服务器先手动连一次主库的3306端口:telnet 192.168.10.11 3306如果通不了,检查防火墙和安全组策略。很多团队数据库服务器走内网IP,配置时用内网IP,但防火墙放行的却是业务网段,导致复制连接被随机丢弃。网络没问题再看账号权限。主库执行:SELECT User, Host FROM mysql.user WHERE User = 'repl';确认账号存在且Host配置正确。然后用从库上的MySQL客户端主动用复制账号连一次主库,验证密码是否正确:mysql -h 192.168.10.11 -u repl -p账号验证通过后,看主库binlog是否还在。如果主库设置了很短的expire_logs_days,而从库断连时间又超过了这个天数,从库请求的binlog文件已经被主库清理,IO线程会报Got fatal error 1236之类的错误,这种情况需要重新全量初始化一次从库,因为缺失的binlog已经无法补救了。5.2 SQL线程报错的恢复与跳过Slave_SQL_Running变No,基本是SQL线程回放binlog时遇到了错误。最常见的两种错误码:1062 Duplicate entry:重复插入。典型的位点重复,比如CHANGE MASTER TO的位点偏前,或之前曾跳过复制后又重新接回。1032 Can't find record:更新或删除的行不存在。典型的从库数据与主库不一致,比如有人违反read_only往从库写了数据。遇到这类错误,第一步是看SHOW SLAVE STATUS\G的Last_Error字段,确认是哪个库哪个表的哪条语句出错。如果是确凿的误操作数据不一致,可以选择跳过该事务继续复制:STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1; START SLAVE;SQL_SLAVE_SKIP_COUNTER=1表示跳过下一个错误事务。这个操作一定要慎用——跳过之后,从库会永久缺失这一条数据,后续如果该数据被其他操作引用,还会引发更大的不一致。我的原则是:跳过之前必须确认这条错误是"脏数据"还是"正常数据"。脏数据(误写入从库的数据导致的冲突)可以跳过;正常业务数据必须停下修复。SQL线程错误后,relay log里那些已经拉下来但还没执行的事务会堆积在本地。如果错误一直不处理,磁盘空间会被relay log占满。处理完成后通常建议执行一次RESET SLAVE或者让SQL线程自动清空已处理的relay log,避免磁盘告警。提示:如果主从数据不一致已经很严重,最稳妥的方式不是一点点修,而是重新做一次全量数据同步并重配复制。省事但绝对安全的方案,没有之一。5.3 延迟治理:复制不能无限追不上主库Seconds_Behind_Master长期大于几十秒,就属于需要介入的延迟了。延迟的来源按优先级排查:首先看主库是不是有大批量写操作。一次UPDATE几十万行的任务执行5分钟,这5分钟里从库SQL线程在追赶一个巨大的binlog事务,期间从库查到的都是旧数据。这种大事务不只是产生延迟,还会让主库的binlog文件瞬间膨胀,从库relay log同样暴增。生产建议:大批量语句拆成小批次执行,控制单事务影响行数。其次看从库服务器负载。SQL线程是单线程回放历史的时代已经过去了,但如果并发参数没调对,从库依然只有一个线程干活。检查:SHOW VARIABLES LIKE 'slave_parallel_workers';如果值是0,把它改成4~8之间。注意该参数的改动只需重启复制线程或者重启从库实例,不需要重新初始化数据。加大并发后,原来一直延迟很久赶不上的从库,通常能在几分钟内追平。最后看从库磁盘IO。如果从库同时还在跑备份任务,磁盘带宽被占用,复制回放的效率会大幅下降。把备份任务调度到复制延迟较小的时段,或者给备份任务做IO限速,都能改善。6. 进阶:GTID与半同步让主从更省心6.1 为什么推荐GTID:从"位点追杀"里解脱传统主从配置用MASTER_LOG_FILE和MASTER_LOG_POS定位复制起点,这在切换和重建从库时非常痛苦。比如从库A宕机,你新加一台从库B,想让它直接从主库拉数据,就必须手动找到主库当前binlog位点填入CHANGE MASTER TO,稍有偏差复制就出问题。GTID(Global Transaction Identifier)从根本上解决了这个问题。每个事务在主库执行时会被分配一个全局唯一的ID,从库通过MASTER_AUTO_POSITION = 1告诉主库"我已经执行到哪些事务了,把后面的事务发给我"。你完全不需要关注binlog文件名和位置,只要配置自动定位。启用GTID需要主从库配置文件中加入:gtid_mode = ON enforce_gtid_consistency = ON然后从库CHANGE MASTER使用:CHANGE MASTER TO MASTER_HOST = '192.168.10.11', MASTER_USER = 'repl', MASTER_PASSWORD = 'StrongPassword_2024', MASTER_AUTO_POSITION = 1; START SLAVE;GTID模式下的主从切换也很直观:如果主库故障要把从库提升为主库,直接执行STOP SLAVE; RESET MASTER;再开放写权限就够了,新主库的GTID集合自然包含旧主库已经执行的全部事务,不会有遗漏。6.2 半同步复制:牺牲一点性能,换来数据不丢纯异步复制有一个天然的窗口:主库提交事务并把binlog发给从库,这期间如果主库宕机,从库没收到这些事务,切换到从库就会丢数据。虽然这个窗口通常只有几毫秒,但对于资金类系统、订单系统来说,任何数据丢失都不能接受。半同步复制(semi-sync replication)通过插件方式实现:主库在事务提交时,必须等待至少一个从库确认收到binlog后才向客户端返回成功。这样主库返回成功时,该事务的binlog必然已经存在于至少一台从库上。如果从库始终不确认,超过超时时间(rpl_semi_sync_master_timeout)后会降级为异步复制,避免从库故障拖死主库。这个退化机制要理解清楚,半同步不是100%主从无延迟,而是尽量缩小丢失窗口。启用半同步前,确认插件文件存在(MySQL 5.7和8.0都内置了semisync插件)。配置:plugin_load_add = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so" rpl_semi_sync_master_enabled = 1 rpl_semi_sync_slave_enabled = 1 rpl_semi_sync_master_timeout = 3000配置完成后重启主从实例,从库还要再STOP SLAVE; START SLAVE;一次,让复制线程重新加载半同步状态。通过SHOW STATUS LIKE 'Rpl_semi_sync_master_status'可确认是否已开启。半同步复制的代价是每次事务提交都要多一次网络往返等待,写入性能会有一定下降,通常可以控制在5%~10%以内。如果业务对写入延迟极其敏感,可以只在核心库或者跨机房的从库上开启,不必全局铺开。7. 最后聊聊我踩过的几个细节坑主从配置本身不复杂,但生产环境的复杂往往来自细节。第一个坑是binlog格式不统一。主库如果之前是MIXED格式,后来只改了ROW格式,但没有把已经执行过的不兼容事务处理掉,从库可能会在某个老事务上报错。改格式最好配合全量重建从库,而不是在一个已有复制链路上直接改。第二个坑是从库提升为主库的角色转换。把从库提升为主库后,如果原主库恢复,又直接把它接回复制链路,会发生"主主冲突"——两台都能写,数据互为副本,很快出现主键冲突。正确处理是原主库降级为从库,并且做好read_only管控,禁止出现双写。第三个坑是复制监控。SHOW SLAVE STATUS\G不会自动告诉你故障,必须有监控报警兜底。最简单的方案是写定时任务,每分钟检查一次状态,检测到IO线程或SQL线程不是Yes,立即告警。主从故障后拖得越久,修复成本越高,最后可能只能重新全量同步数据。做mysql主从配置这件事,我个人的体会是:不要把它当成一次性的搭建任务,而要当成一个持续的运维习惯。从配置完成那一刻起,定期检查延迟、检查binlog保留时长、演练从库提升流程,这些才真正决定主从架构能不能在危机时刻扛住。如果你刚开始接触,照着本文的步骤把一套测试环境搭起来、再故意制造几个故障练手排错,一次完整的实操胜过十遍文档阅读。