
1. 先搞清楚主从一致到底在解决什么问题MySQL主从复制这套东西说简单也简单说复杂也复杂。我见过不少团队在主从复制上栽跟头数据对不上、主从延迟飙到几十秒、从库被误写了数据导致复制中断、切换主库之后丢数据……这些问题大多不是因为MySQL本身多难而是配置的时候没有把整个链路想明白。先给还没上过车的朋友说清楚MySQL主从复制本质上是让一台主库把所有的写操作以二进制日志的形式记录下来然后从库通过网络把这些日志拉过去在自己本地重新执行一遍。这样就实现了主库和从库的数据一致。日常最常见的用途有三个一是读写分离把读流量打到从库上减轻主库压力二是高可用主库挂了可以切换从库顶上三是数据备份在不影响主库的情况下用从库做备份。这篇文章我不打算讲那种“你复制一段配置粘贴进去就能跑”的教程——网上太多了但很多人照着配完还是出问题因为不理解每一步为什么要这么做。我把自己在生产环境里踩过的坑、验证过的方案、测试过的参数从头到尾捋一遍。适用对象是正在给公司搭主从复制环境的运维或后端研发以及在本地想用主从环境做实验的学习者。不管你是MySQL 5.7还是8.0原理是一样的只是个别参数写法略有差异。先说个总的路线图避免你配到一半迷失方向主库开启binlog并创建复制账号从库配置server_id和relay log然后通过CHANGE MASTER TO把从库指向主库启动复制最后用状态命令验证复制是否正常。但真正落地的时候每一步都有讲究。2. 方案选型GTID还是传统位点复制2.1 两种复制方式的本质区别很多新手第一次接触主从配置会看到两种写法一种是CHANGE MASTER TO MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POS154这种基于日志文件位置的方式另一种是CHANGE MASTER TO MASTER_AUTO_POSITION1这种基于GTID的方式。这里我强烈建议新环境直接用GTID。GTID的全称是Global Transaction Identifier全局事务标识符。每个事务在主库上执行时都会分配一个唯一的ID这个ID由source_id:transaction_id组成比如4a0f0a77-3a2e-11ef-9a3c-00163e123456:1。因为每个事务有全局唯一编号从库在执行时就能明确知道自己已经执行到哪个事务了主从之间天然建立起对应关系。传统位点方式的问题在于如果从库的binlog文件被清理了或者主库做过某些操作导致位点失效你得重新去找位点如果有多个从库每个从库都要手工记录和维护位点信息。GTID方式只要MASTER_AUTO_POSITION1从库会自动把自己已经执行过的GTID集合发给主库主库自动推送缺失的事务。省心太多了。2.2 如何选择MySQL版本版本选择上我建议新项目直接用MySQL 8.0或8.4。MySQL 5.7目前已经EOL官方连bug fix都不出了虽然很多老系统还在用但真的不该再作为新环境的选择。GTID在5.7里其实已经支持但8.0的GTID机制更加完善比如mysql.gtid_executed表的处理、复制崩溃恢复的机制都不需要额外配置。还有一个重要的点是8.0默认的认证插件是caching_sha2_password而从库连接主库复制账号时主库的mysql_native_password插件在8.0里已经废弃必须用caching_sha2_password。这个细节很多人配的时候没注意导致复制账号创建完了一直报认证错误。生产环境我见过的主从版本组合主库8.0.34、从库8.0.34完全一致这是最稳妥的。如果主从版本不一致比如主库5.7、从库8.0虽然官方说支持但会遇到很多不可预期的问题比如binlog格式差异、系统表结构不同。我的建议是能不跨版本就不跨版本实在要跨也一定要从库版本不低于主库并且先做充分的兼容性测试。3. 环境准备安装、初始化和基础调优3.1 MySQL的安装方式和选择依据配置主从之前得先把MySQL跑起来。安装方式不外乎三种yum/apt包管理器安装、docker容器部署、二进制TAR包解压。这里我给不同场景的建议用服务器生产环境用系统包管理器装最省心因为systemd服务脚本、配置文件路径、日志轮转这些都是自动的不需要手动维护。CentOS/RHEL系列直接加官方yum源mysql80-community然后yum install mysql-server。Ubuntu/Debian系列用APT源安装mysql-server。本地学习或者想快速起一套测试环境Docker最方便。一条docker run命令就能起一个MySQL实例主从的话起两个容器通过自定义网络互联。但注意Docker里的MySQL数据目录最好挂载到宿主机不然容器删了数据就没了。我自己习惯在Docker里做实验因为环境干净出了问题直接删了重建非常高效。二进制TAR包解压安装适合那种有特殊要求、需要严格控制目录结构的环境。但说实话如果没特殊需求没必要用这种方式给自己增加工作量。3.2 关键初始化参数不能漏说个很常见的坑很多人安装完MySQL之后没有修改字符集和排序规则就直接开始配主从。看上去问题不大但等从库开始追binlog遇到中文字符或者emoji的时候会出现字符集转换乱码甚至复制报错。MySQL 8.0默认字符集是utf8mb4排序规则是utf8mb4_0900_ai_ci这其实还行。但5.7默认是latin1这就必须改了。建议在my.cnf的[mysqld]段统一设置character_set_server utf8mb4 collation_server utf8mb4_0900_ai_ci还有一个参数是lower_case_table_namesLinux上默认是0即区分大小写Windows上默认是1。如果主从跨平台部署这个参数必须保持一致否则从库在同步带有大写字母的表名时会直接报错找不到表。我踩过这个坑主库Windows表名是小写从库Linux结果一同步就各种Table xxx doesnt exist。最后统一设置为lower_case_table_names1才解决。3.3 初始化MySQL实例并设置root密码装完MySQL第一件事就是初始化。用包管理器安装的基本上装完就自动初始化好了但如果你用二进制包需要手动执行mysqld --initialize --usermysql --basedir/usr/local/mysql --datadir/data/mysql注意--initialize会在日志里生成一个临时root密码初体验密码在日志文件里通常是/var/log/mysqld.log或/data/mysql/error.log搜一下temporary password就能看到。登录之后要立刻改密码ALTER USER rootlocalhost IDENTIFIED BY YourStrongPassword123!;这一步很多人会在这里卡住因为MySQL 8.0默认有密码复杂度校验插件validate_password简单密码会被拒绝。要么设置一个复杂密码要么降低校验强度。生产环境我不建议关掉校验但在本地实验环境为了省事可以先关掉SET GLOBAL validate_password.policy LOW; SET GLOBAL validate_password.length 6;还有一件事如果要用root去配置复制我是反对的。应该专门为复制创建一个最小权限账号这样才能控制风险。后面我会细讲。4. 主库配置开启binlog和GTID4.1 主库my.cnf参数逐行解释主库的配置文件是整条复制链路的起点。下面是我生产环境用的主库配置每一行都有它的意义[mysqld] server-id 1 log_bin /data/mysql/binlog/mysql-bin binlog_format ROW binlog_row_image FULL gtid_mode ON enforce_gtid_consistency ON log_slave_updates ON expire_logs_days 7 max_binlog_size 1Gserver-id这是实例在整个复制拓扑里的唯一标识。主库从库都不能重复重复的id会导致复制链路混乱从库会报Duplicate server id错误。这个id是0到4294967295的整数没有强制从1开始但建议规划好比如主库1、从库2、从库3。log_bin开启binlog并指定日志文件路径。这里我不建议用配置文件里的相对路径最好是一个独立的binlog目录避免和数据文件混在一起。我之前遇到过日志目录满了导致主库只读的惨案把binlog放到独立分区能有效隔离风险。binlog_format ROW这是MySQL 8.0的默认值也是我强烈推荐的表单。还有STATEMENT和MIXED两种格式。STATEMENT记录的是SQL语句本身同步的时候从库重新执行一遍ROW记录的是每一行数据的变更。ROW格式最大的优点是准确不会因为SQL执行环境差异导致数据不一致缺点是binlog体积会变大。当前这个时代磁盘便宜、主从延迟要求高无脑选ROW就好。binlog_row_image FULL这条参数和ROW格式配合使用。FULL表示binlog里记录行的完整前后镜像这样即使从库没有唯一索引也能通过完整字段匹配行。它的代价是binlog体积更大但为了复制可靠性值得。gtid_mode ON和enforce_gtid_consistency ON这两条是一起开的不能只开一个。GTID模式开启后所有事务都有全局唯一ID从库可以根据GTID精确判断需要同步哪些事务。enforce_gtid_consistency则保证非事务性的操作比如CREATE TABLE ... SELECT也能安全使用GTID。log_slave_updates ON这条参数很关键它决定从库在回放主库日志时是否也把自己执行的事务写进自己的binlog。什么场景需要如果你的拓扑是级联复制也就是A - B - C那么B必须开这个参数否则C从B同步不到任何数据。即使你目前只有一层主从我也建议提前打开为后续扩容留余地。expire_logs_days 7binlog保留天数。这个值不要设太小否则从库宕机超过7天binlog被清理后从库就彻底追不上了需要重建。但也不要设太大否则磁盘会被binlog塞满。7天是个还不错的默认值如果你的从库经常宕机可以考虑14天甚至更长。4.2 主库必备的复制账号和权限主库上需要创建一个专门给从库用的账号。这里注意复制账号只需要REPLICATION SLAVE权限不需要给SUPER或者ALL权限保证最小权限原则CREATE USER repl192.168.1.% IDENTIFIED BY Repl123456; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl192.168.1.%; FLUSH PRIVILEGES;主机部分建议限制网段不要写成%避免任何主机都能用这个账号连上你的主库。如果是从库IP不固定至少也限制成内网IP段。REPLICATION CLIENT权限是可选的它的作用是让这个账号可以执行SHOW MASTER STATUS这类查看状态的命令方便排查问题建议加上。设置完账号之后验证一下权限是否生效以及远程连接是否正常mysql -h主库IP -urepl -p -e SHOW MASTER STATUS;如果这一步报错先看是不是账号主机限制的问题再看MySQL的bind-address参数是否允许外部IP连接。MySQL默认可能只绑定了127.0.0.1需要在my.cnf里设置bind-address 0.0.0.0才能让从库连上或者精确绑定到内网IP。4.3 主库重启和数据备份双保险修改完my.cnf之后需要重启MySQL服务让参数生效systemctl restart mysqld重启之后检查binlog是否正常开启SHOW MASTER STATUS;这一步能输出当前正在写的binlog文件名和位点。比如------------------------------------------------------------------------------- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | ------------------------------------------------------------------------------- | mysql-bin.000003 | 157 | | | 4a0f0a77-3a2e-11ef-9a3c-00163e123456:1-2 | -------------------------------------------------------------------------------这里有两个关键信息File和Position。如果用的是传统位点复制需要把这个位置记录下来给从库用。如果用GTID其实不太需要但从0开始理解整条链路还是有用的。在正式配置从库之前还有一个非常重要的工作保证主库当前的数据能完整地同步到从库。如果你的主库已经运行了一段时间里面有存量数据直接从空从库开始同步是肯定不行的。需要先对主库做一次备份然后在从库上恢复。推荐的备份方式如果数据量不大用mysqldump最直接。这里关键不是备份命令本身而是要保证备份的数据和binlog位点是严格对应的。操作流程是在备份期间给主库加只读锁保证备份的数据是一致的快照FLUSH TABLES WITH READ LOCK;另外开一个MySQL会话记录当前的binlog位置SHOW MASTER STATUS;另外用mysqldump做全量备份mysqldump -uroot -p --single-transaction --master-data2 --all-databases backup.sql注意--single-transaction对InnoDB表做一致性快照备份不会长时间锁表--master-data2参数会在备份文件里自动写入MASTER_LOG_FILE和MASTER_LOG_POS信息而且是以注释形式写在备份文件的头部这样即使你忘了记录位置也能从备份文件里查。备份完成后释放主库的只读锁UNLOCK TABLES;注意如果主库数据量大备份过程持续时间较长FLUSH TABLES WITH READ LOCK锁表时间过长会影响线上写入。生产环境建议用专业工具比如Percona XtraBackup做在线备份几乎不影响业务。本地实验环境用mysqldump就够了。5. 从库配置参数、导入和启动复制5.1 从库配置文件的关键差异从库的配置和主库有几个关键差异[mysqld] server-id 2 log_bin /data/mysql/binlog/mysql-bin binlog_format ROW binlog_row_image FULL gtid_mode ON enforce_gtid_consistency ON log_slave_updates ON relay_log /data/mysql/relaylog/mysql-relay-bin read_only ONserver-id绝对不能和主库一样我这里设置成2。relay_log从库专属的中继日志。主库的binlog传到从库之后先写入本地的relay log然后由SQL线程读取并执行。中继日志的目录最好也独立出来不要和数据目录混杂。read_only ON强制从库数据库只读不允许任何非超级权限账号进行写操作。这个参数有个特点它不会阻止超级权限账号比如root写入也不会阻止复制线程写入。所以如果你在从库用root手工改了一条数据复制线程照样可以执行binlog里的回放。但是它能够有效防止应用因为连接串配错把写流量打到从库上——这种事故我见过太多次了从库一旦被写了主库没有的数据复制会直接报错中断非常头疼。log_slave_updates ON从库在执行完主库的事务后也把执行结果写入自己的binlog。这是为级联复制准备的就算现在用不上也建议开着。唯一的例外是如果你确定从库仅仅是只读备份机绝对不会有其他从库再挂在它下面那log_slave_updates可以关掉能省下一点磁盘和IO开销。但我的建议还是开着万一以后要加从库不用改配置重启。另外如果从库和主库是同一台物理机上的多实例部署比如用Docker和mysqld_multi每个实例必须确保数据目录、日志目录完全隔离互不干扰。5.2 从库导入数据和配置复制链路配置完从库并重启之后第一步要做的不是设置复制而是把主库的备份数据导进来。这样才能保证从库的初始状态和主库一致mysql -uroot -p backup.sql导入之后做一个基本验证比如在主库上执行过的一些关键表在从库上查一下行数是否一致SELECT COUNT(*) FROM testdb.users;如果数据量对不上先别往下走排查是不是备份过程出了问题。数据基础都没打牢后面复制再正常也是白搭。现在来到最关键的一步让从库知道要去哪里拉日志。有两种写法我这里分开讲。使用GTID方式STOP SLAVE; CHANGE MASTER TO MASTER_HOST192.168.1.10, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDRepl123456, MASTER_AUTO_POSITION1; START SLAVE;使用传统位点方式STOP SLAVE; CHANGE MASTER TO MASTER_HOST192.168.1.10, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDRepl123456, MASTER_LOG_FILEmysql-bin.000003, MASTER_LOG_POS157; START SLAVE;两种情况都要先执行STOP SLAVE虽然新配置的实例实际上IO线程和SQL线程还没启动但养成这个好习惯可以避免在修改配置时出现状态冲突。如果你是先做的备份后启动复制而且备份时用了--master-data2传统位点的两个参数可以直接从备份文件头部找到head -50 backup.sql | grep MASTER_LOG_FILEGTID方式能不能用前提是主库也得开启GTID。如果你的主库之前是用传统位点方式跑的后来想平滑过渡到GTID官方文档有完整的步骤但比较复杂。我的建议是新环境一次性配好GTID别以后折腾。5.3 从库复制状态怎么看配置完成后检查复制是否正常运行SHOW SLAVE STATUS\G输出内容非常多重点看以下几行Slave_IO_Running: YesIO线程是否正常连接到主库并拉取binlog。Slave_SQL_Running: YesSQL线程是否正常回放relay log。Seconds_Behind_Master: 0从库延迟了多少秒执行主库事务。为0表示完全追上。Last_IO_Error和Last_SQL_Error最近一次IO或SQL线程报错信息。没报错就显示为空。Retrieved_Gtid_Set和Executed_Gtid_SetGTID模式下显示从库已经拉取和已经执行的事务集合。Master_Log_File和Read_Master_Log_PosIO线程正在读取主库binlog的文件名和位置。Relay_Log_File和Relay_Log_PosSQL线程正在读取relay log的位置。两个线程推进的差距可以反映延迟情况。两个列显示Yes是最基本的要求但不是说Yes了就万事大吉。建议再做一个实际的数据验证在主库上新建一个测试表CREATE TABLE testdb.repl_test (id INT PRIMARY KEY, name VARCHAR(50)); INSERT INTO testdb.repl_test VALUES (1, hello replication);在从库上查询SELECT * FROM testdb.repl_test;能看到那条数据说明整条链路是通的。这个方法虽然简单但能有效验证主从复制的配置是否正确比单纯看状态列更有说服力。6. 一致性验证不只是看Slave_Status6.1 延迟监控的正确姿势很多人只会看Seconds_Behind_Master我告诉你这个值有时是不靠谱的。它表示的是SQL线程当前执行的事务时间戳与IO线程拉取到的最新事务时间戳之差。如果从库IO线程拉取binlog的能力很慢或者网络有抖动这个值可能会暂时为0但实际上数据已经落后了很多。因为它衡量的是从库本地的时间差不是主从之间的真实数据差。更可靠的方式是在主库上创建一个心跳表。主库每隔一段时间更新这张表的update_time字段从库查询这张表计算时间差。比如CREATE TABLE heartbeat ( id INT PRIMARY KEY, update_time DATETIME );主库上定期执行UPDATE heartbeat SET update_time NOW() WHERE id 1;从库上查询SELECT TIMESTAMPDIFF(SECOND, update_time, NOW()) AS delay FROM heartbeat WHERE id 1;这个值才是真正的业务视角延迟。虽然这种监控需要自己开发但效果比看状态列准得多。生产环境我也建议配合第三方监控如Prometheus mysqld_exporter来做持续观测。6.2 数据一致性的专项校验等到复制跑了一段时间你需要验证主从两边的数据是否完全一致。尤其是在从库被意外写入、复制中断恢复后单纯靠状态列无法发现存量数据的差异。这时候需要工具帮忙。Percona Toolkit里的pt-table-checksum是最常用的数据一致性校验工具。用法pt-table-checksum --host主库IP --userroot --passwordxxx --databasestestdb --replicatetestdb.checksum它在主库上计算每个表的checksum然后在从库上验证最终报告哪些表存在差异。这个工具的原理比较简单对每行数据的每个列做哈希聚合再对比主从两边算出的校验值是否一致。一旦发现不一致再配合pt-table-sync进行修复。自带的方案也有MySQL 8.0的NDB集群有专门的校验命令但社区版的普通主从里官方没有提供内置的一致性校验工具这就是为什么Percona Toolkit几乎是DBA标配。我自己常用的经验是增量变更频繁的核心表每天校验一次普通表每周一次。校验结果如果出现差异要区分是复制延迟导致的暂时性差异还是真正的数据不一致——跑之前先确认从库延迟已经追平。6.3 从库只读策略的测试配置了read_only ON之后一定要亲自验证一下从库的只读是否真正生效。用应用账号连接从库执行写入INSERT INTO testdb.repl_test VALUES (2, should fail);如果报错The MySQL server is running with the --read-only option说明配置生效。如果插入成功了说明你不是用了超级权限账号登录就是read_only没有设到[mysqld]段而是在其他地方设的值。检查一下。这里有个细节read_only只对普通用户生效对超级权限用户SUPER权限或拥有SYSTEM_VARIABLES_ADMIN权限的用户无效。所以在生产环境即使开了read_only也不要随便把超级权限账号交给应用去连从库。7. 常见问题与排查技巧实录7.1 认证插件导致的复制连接失败MySQL 8.0的默认认证插件是caching_sha2_password从库连接主库复制时如果主库用这个插件创建的账号从库可能需要加密连接才能通过认证。具体表现是从库的SHOW SLAVE STATUS里Last_IO_Error: error connecting to master repl主库IP:3306 - retry-time: 60 retries: 10 message: Authentication plugin caching_sha2_password cannot be loaded排查方式在主库上执行SELECT plugin FROM mysql.user WHERE userrepl;看这个账号用的什么认证插件。如果是caching_sha2_password方法一是确保主库的网络连接启用了SSL方法二是在创建账号时显式指定CREATE USER repl192.168.1.% IDENTIFIED WITH mysql_native_password BY Repl123456;但注意8.0后期版本mysql_native_password已经被默认禁用不推荐再用。最稳妥的做法是保证从库到主库的TCP连接支持RSA公钥传输或者在CHANGE MASTER TO里加上MASTER_SSL1。具体要看你的网络环境是否允许加密连接。7.2 SQL线程报错主键冲突和记录不存在SQL线程报错11062记录不存在或者1062主键冲突是主从复制里最常见的两个故障。比如主键冲突Last_SQL_Error: Could not execute Write_rows event on table testdb.users; Duplicate entry 1001 for key PRIMARY这说明从库已经有一条主键为1001的数据但主库又在尝试插入这条。原因基本都能追溯到从库被手工写入过、或者从库恢复了旧的备份集导致数据和主库时序错乱。解决办法是先把问题数据处理掉-- 先停掉SQL线程防止错误的操作被继续执行 STOP SLAVE SQL_THREAD; -- 检查冲突数据确认从库的这行数据到底该不该存在 SELECT * FROM testdb.users WHERE id 1001; -- 手工处理如果这一行是错误的删除它 DELETE FROM testdb.users WHERE id 1001; -- 恢复复制 START SLAVE SQL_THREAD;如果是主库执行了删除从库却找不到这行记录1032错误处理思路一样先确认这条数据是否应该存在于从库。如果确认从库缺失的数据应该在那就要考虑从主库单独补数据而不是盲目跳过错误。官方推荐跳过错误的方式是sql_replica_skip_counter5.7里叫sql_slave_skip_counterSTOP SLAVE SQL_THREAD; SET GLOBAL sql_replica_skip_counter 1; START SLAVE SQL_THREAD;这个参数的作用是让SQL线程跳过下一条没有被成功执行的日志事务。注意一次跳过一个事务而不是跳过一条日志因为一个事务可能包含多条日志。跳过之后要立刻查看下一个错误是什么确认不再报错才算处理完。7.3 relay log损坏和复制中断恢复如果从库宕机relay log损坏的可能性不小。表现是SQL线程报错打开relay log文件发现内容异常。这时候可以重建relay log不必重新做全量备份STOP SLAVE; RESET SLAVE ALL; CHANGE MASTER TO ...(重新指向主库); START SLAVE;RESET SLAVE ALL会清掉从库上现有的relay log和主库连接信息相当于把复制的连接配置重置。如果从库上面有已经执行到一半的relay logGTID模式下从库会根据Executed_Gtid_Set自动判断哪些事务已经执行过重新从主库拉取缺失部分不会重复执行已经完成的事务。但如果数据不一致已经存在重建复制前最好先做一次全量一致性校验确保从库数据没被破坏过。7.4 主从延迟的常见原因主从延迟是排查频率最高的问题。除网络本身慢之外最常见的原因第一个是主库大事务。比如一次性UPDATE几百行在ROW格式下binlog里会记录每一行变更的前后镜像从库需要逐行回放这个压力是实打实的。建议拆分大事务每个事务处理的数据量控制在小范围内。第二个是从库硬件比主库差。有的团队给主库配置了SSD加高内存从库却用普通机械硬盘这等于拿自行车追跑车延迟不才怪。从库的硬件至少不能比主库差太多。第三个是sync_binlog和innodb_flush_log_at_trx_commit参数设置不一致。主库为了性能可能设置成0或2但从库承担读流量可以考虑调高写入刷盘频率来减少延迟。第四个是单线程SQL回放的瓶颈。MySQL 5.6之前只有单线程8.0之后引入了MTS多线程从库可以配置并行回放。调整方式[mysqld] slave_parallel_type LOGICAL_CLOCK slave_parallel_workers 4但并行回放也不是万能的如果是单个大事务并行度再高也没用。7.5 常见问题速查表症状可能原因排查方法解决方案IO线程无法连接主库网络不通、认证失败、主库bind-address限制用mysql客户端手动连接主库测试检查防火墙、账号密码权限、bind-addressSQL线程报1062主键冲突从库被写入、主从数据未对齐查看错误上下文和冲突数据手工处理冲突数据必要时用工具校验全库SQL线程报1032记录不存在主库删除记录但从库缺失查看错误日志上下文从主库取对应数据补入或确认合理后跳过主从延迟持续增大大事务、硬件差异、网络带宽查看状态列和时间戳对比拆分事务、优化从库内存配置、考虑并行回放slave status显示空智能CHANGE MASTER未生效查看是否有过往配置残留先STOP SLAVE再重新CHANGE MASTER两个server_id重复配置复制了同样的id查看my.cnfshow variables主从配置互不相同的server-id7.6 我踩过的几个坑总结说几个文字教程里很少提到的实操细节都是我在真实环境里碰到的第一个是CHANGE MASTER TO之前从库上如果有历史复制配置残留而你没有先执行STOP SLAVE有时候新的配置不会生效SHOW SLAVE STATUS显示的还是旧的master信息。我的习惯是先STOP SLAVE;再RESET SLAVE ALL;然后重新配置。虽然步骤繁琐一点但能避免很多莫名其妙的状况。第二个是防火墙。很多从库连接不上主库排查了半天发现是防火墙没放行3306端口。CentOS上用了firewalld的话firewall-cmd --permanent --add-port3306/tcp firewall-cmd --reload云服务器还要检查安全组规则。这个问题低级但极其常见。第三个是主库的max_allowed_packet太小从库同步大对象的时候失败。如果主库备份后导入从库备份文件里有大数据字段max_allowed_packet不够会直接报错。建议主从的max_allowed_packet都设置为64M以上。第四个是sql_mode不一致。主库sql_mode如果有STRICT_TRANS_TABLES从库没有两边对于同一句SQL的执行结果可能不同久了数据就偏了。配置主从环境时sql_mode一定要保持一致。8. 用GTID方式配置一次完整的主从复制实操复盘这个章节我打算用一个完整的最小实验从零开始跑通GTID主从复制适合刚入门或者准备在本地环境做实验的同学直接对照操作。我会用Docker来做环境干净方便清理。先起两个MySQL容器分别模拟主库和从库docker network create mysql-repl-net docker run -d --name mysql-master \ --network mysql-repl-net \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEtestdb \ mysql:8.0.36 \ --server-id1 \ --log-binmysql-bin \ --gtid_modeON \ --enforce-gtid-consistencyON \ --log-slave-updatesON docker run -d --name mysql-slave \ --network mysql-repl-net \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ mysql:8.0.36 \ --server-id2 \ --relay-logmysql-relay-bin \ --gtid-modeON \ --enforce-gtid-consistencyON \ --log-slave-updatesON \ --read-onlyON然后进入主库容器创建复制账号docker exec -it mysql-master mysql -uroot -proot123CREATE USER repl% IDENTIFIED BY Repl123456; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl%; FLUSH PRIVILEGES;这里%只是为了本地实验方便生产环境一定要限制IP。接着在主库插入模拟数据USE testdb; CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50)); INSERT INTO users (name) VALUES (Alice), (Bob), (Charlie);然后进入从库容器配置连接主库并启动复制docker exec -it mysql-slave mysql -uroot -proot123STOP SLAVE; CHANGE MASTER TO MASTER_HOSTmysql-master, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDRepl123456, MASTER_AUTO_POSITION1; START SLAVE;注意这里用的是容器名mysql-master作为主机地址因为这两个容器在同一Docker网络里可以互相通过服务名访问。然后检查状态SHOW SLAVE STATUS\G重点看Slave_IO_Running和Slave_SQL_Running是否都是Yes。现在验证数据同步在主库插入一条新记录然后去从库查询-- 主库执行 USE testdb; INSERT INTO users (name) VALUES (Dave);-- 从库执行 USE testdb; SELECT * FROM users;能看到Dave出现在从库的查询结果里复制成功。这个实验我没有用mysqldump做全量备份导入是因为容器启动时从库是空库复制启动后主库现有的数据会通过binlog推送到从库。但现实中的主库曾经有过历史事务binlog大概率早就被清理了从库如果直接复制会缺失很多数据。所以生产环境的正确流程仍然是先全量备份恢复再开启复制。实验环境干净才能直接跑。最后把这个环境清理掉避免占资源docker stop mysql-master mysql-slave docker rm mysql-master mysql-slave docker network rm mysql-repl-net9. 主从一致性的更高要求半同步复制和MySQL组复制9.1 传统异步复制的短板上面讲的配置全部是基于MySQL默认的异步复制。异步复制意味着主库事务提交成功后并不关心从库到底有没有收到binlog。如果主库刚提交完一个事务就宕机而binlog还没传送到从库那一部分数据就丢失了。在高可用切换的场景这种丢失极其致命。举一个生活化的类比异步复制就像寄平信寄出去就完了邮局丢不丢信不归你管半同步复制则是寄挂号信要求邮局给你签收回执确认对方收到你才算寄出成功。9.2 半同步复制配置方式半同步复制Semisynchronous Replication需要安装插件。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;注意MySQL 8.0中插件名变成了rpl_semi_sync_source和rpl_semi_sync_replica5.7中是rpl_semi_sync_master和rpl_semi_sync_slave别搞混了。插件安装后主库在提交事务时会等待至少一个从库确认已经收到binlog然后才向客户端返回成功。如果等待超时主库会自动退化为异步复制模式日志里会有告警。半同步复制还有个性能考量主库每次事务提交都要多等一个网络往返延迟会比异步复制高一些。在高并发写场景要在数据安全性和延迟之间做权衡。9.3 需要更高可用性的场景考虑Group Replication半同步复制虽然比异步复制安全但还是有单点问题如果唯一确认收到binlog的从库宕机主库可能会退化到异步模式。如果业务要求更高的数据安全级别可以考虑MySQL Group ReplicationMGR也就是组复制。它是基于共识算法Paxos变种的高可用方案允许多个节点共同组成一个复制组事务需要在组内多数派节点确认后才能提交从而保证强一致性。但MGR对网络环境要求极其苛刻需要稳定的低延迟网络节点之间通信频繁。配置复杂度也远高于传统主从复制。我的建议是核心业务、预算充足、有专业DBA团队的公司再考虑MGR一般场景做好半同步复制加上定期全量校验已经足够。9.4 做决定的参考对照方案数据一致性故障恢复复杂度写性能影响适用场景异步复制可能丢事务中无读多写少、读一致性要求不高的系统半同步复制至少一个从库确认丢数据概率极低中有额外网络延迟电商支付、金融系统等对数据安全要求高的场景组复制(MGR)多节点强一致高更高延迟需要多数派响应核心交易型系统对可用性要求极高的场景以我自己的经验大部分互联网应用的读写分离场景异步复制加半同步补充已经完全够用没必要为了分布式而分布式。10. 最后再分享一点个人体会做为主从复制的重度使用者我最大的感悟是这个系统不复杂但它由很多小细节组成任何一个小细节出错复制链路都会以各种形态表现出来。而且错误往往不在你配置的那一步而在前期的基础环境。比如字符集不一致、防火墙没放行、账号权限不够、binlog格式设错都是等配置完成之后才暴露问题。所以我在每次配置主从之后都会做三件事当成本能动作第一马上做一次数据写入验证确认新数据能同步过去第二检查一遍主从的sql_mode、character_set_server、lower_case_table_names三个关键变量是否一致第三设置一个延迟和错误告警能在第一时间发现复制中断。配置主从复制就像修一条水管接口对准只是一部分更重要的是保证整条管道的材质、压力、流量都匹配。希望这篇分享能让你少走几步弯路把主从复制一次配稳。后面如果遇到具体的报错信息欢迎在评论区带上现象和日志讨论我看到了会来帮忙看两眼。