资讯动态

Ubuntu上MySQL 8.0 GTID半同步主从复制搭建指南

发布时间:2026/10/17 9:20:25 来源:尧图企业网站定制
前一阵需要在一台Ubuntu 25.10的干净服务器上搭一套可靠的MySQL主从方案用于读写分离和故障切换演练。一开始也想过直接使用apt安装MySQL但评估之后还是走了二进制安装路线复制模式直接定为GTID半同步主从。这篇就把整个过程完整记录一下包括二进制安装、初始化、GTID复制搭建、半同步插件配置以及我在实际部署中遇到的问题和排查链路给后面要走同样路线的同学一个参考。这种组合算不上“炫技”但每一步都有讲究Ubuntu 25.10本身的软件源默认版本不一定满足需求用官方二进制包能固定版本、固定目录结构GTID复制比传统基于binlog坐标的复制省心太多半同步则是在异步复制和全同步复制之间取了一个更合理的权衡点。三件事拆开做都不难连起来踩一遍才会发现很多文档里没写清楚的细节。1. 为什么绕开apt默认源二进制安装的三个理由很多人在Ubuntu上装MySQL第一反应就是apt install mysql-server。这在测试环境确实省事但我这次没有这么做原因有三。第一版本不可控。Ubuntu软件源里的MySQL版本和官方Linux通用二进制包不一定同步项目里如果明确要求跑某个小版本或者后续要跨小版本迁移用apt装的版本会受到发行版发布周期的限制。二进制tar包是从MySQL官方“Linux x86_64”通用包拉下来的压缩包版本是多少装出来就是多少整个生命周期里都清楚自己在跑什么。第二路径和配置过于分散。apt安装会把mysqld放到/usr/sbin数据目录默认在/var/lib/mysql配置文件分散在/etc/mysql/下的多个文件里。一旦要做多实例、做迁移、做双主这种分散结构会让你到处找配置。二进制方式是所有东西都以basedir为根数据目录可以独立规划配置集中在/etc/my.cnf排障和复制迁移都很直观。第三依赖关系干净。apt装MySQL会拖入一堆和业务无关的init脚本、日志轮转、AppArmor配置有些环境里这些“贴心行为”反而成为后续改装的阻碍。官方通用二进制包只要求基础动态库存在对系统的侵入性小很多。对于需要在多台机器上快速铺开同样版本的环境复制整个目录加一份my.cnf就能搞定比反复apt install并改动配置高效得多。从实际维护经验来看如果一台机器上已经装了apt版本之后再想把它平移到自定义目录往往要手动处理数据目录迁移、卸载残留、服务文件替换成本不低。所以新环境直接上二进制包后面省掉很多隐形麻烦。2. Ubuntu 25.10上的二进制安装与初始化细节2.1 下载、解压与目录规划以当时最新版的MySQL 8.0.x系列为例下载官方“linux-glibc2.17”对应的tar包即可。Ubuntu 25.10的glibc版本远高于2.17向下兼容没有问题。下载完成后打算把release包解压到/usr/local并通过软链固定一个稳定路径这样后续升级只需要换软链。cd /usr/local sudo tar -xJf mysql-8.0.x-linux-glibc2.17-x86_64.tar.xz sudo ln -sfn mysql-8.0.x-linux-glibc2.17-x86_64 mysql数据目录我习惯单独放到/data/mysql/databinlog和relay log也放在同一块数据盘下便于后续做磁盘容量规划。创建专用系统用户并把数据目录所有权交过去sudo groupadd mysql sudo useradd -r -g mysql -s /sbin/nologin mysql sudo mkdir -p /data/mysql/data sudo chown -R mysql:mysql /data/mysql依赖检查这一步很多人忽略导致mysqld一启动就报缺共享库。执行cd /usr/local/mysql ldd bin/mysqld | grep not found正常情况下会出现libaio.so.1之类的输出说明需要补装libaio。Ubuntu 25.10上对应的包名可能叫libaio1t64安装命令是sudo apt update sudo apt install -y libaio1t64 libnuma1装完再跑一次ldd bin/mysqld | grep not found确认没有缺失项。这个动作虽然简单但能帮你把后续“mysqld启动失败”的一大半问题提前消灭在源头上。2.2 my.cnf配置与实例初始化二进制安装下我建议直接用/etc/my.cnf作为唯一配置入口。一个最小可用的配置文件长这样[mysqld] basedir /usr/local/mysql datadir /data/mysql/data socket /data/mysql/data/mysql.sock pid-file /data/mysql/data/mysqld.pid log-error /data/mysql/data/mysql-error.log port 3306 user mysql server-id 1 log-bin mysql-bin binlog_format ROW gtid_mode ON enforce-gtid-consistency ONserver-id、log-bin、binlog_format、gtid_mode、enforce-gtid-consistency这几个参数并不是现在就要用但建议在初始化之前就写进去。原因是GTID相关的开关如果先默认关闭之后系统已经有事务了再动态开启虽然也能通过OFF_PERMISSIVE到ON_PERMISSIVE再到ON做平滑切换但多一事不如少一事。全新环境直接在初始化阶段打开后面省心。初始化使用mysqld --initialize注意这一步不是启动服务而是创建数据字典和系统库sudo -u mysql /usr/local/mysql/bin/mysqld --defaults-file/etc/my.cnf --initialize这里有一个官方二进制包特有差异用--initialize会生成一个临时root密码写在log-error文件里比如grep temporary password /data/mysql/data/mysql-error.log然后启动MySQL稍等几秒后再登录修改密码。如果用实验环境且不关心安全性也可以改成--initialize-insecure这会生成空密码的root账号方便首次登录。生产环境我建议老老实实用带临时密码的方式并把修改后的密码放进密钥管理别散落在命令行历史里。初始化如果报了“--initializefailed but the error log file is clos”大概率是datadir目录不为空或者目录owner不对先把/data/mysql/data清空并重新chown mysql:mysql再跑一次。2.3 systemd服务文件和首次登录为了方便开机自启和统一管理我写了一个简单的systemd unit而不是用mysqld_safe[Unit] DescriptionMySQL 8.0 Server Afternetwork.target [Service] Usermysql Groupmysql Typesimple ExecStart/usr/local/mysql/bin/mysqld --defaults-file/etc/my.cnf LimitNOFILE65535 Restarton-failure [Install] WantedBymulti-user.target这里有一个非常关键的点[Service]里的Usermysql一旦指定mysqld在运行过程中就是以mysql用户身份工作。如果你的my.cnf里没有写usermysql那么systemd会以mysql身份启动但进程可能尝试切换回root导致权限报错反过来如果my.cnf写了usermysqlsystemd也写了Usermysql两边是一致的没问题。我习惯保留两处都是mysql保证启动行为一致。然后sudo systemctl daemon-reload sudo systemctl enable mysqld sudo systemctl start mysqld如果启动失败先看/data/mysql/data/mysql-error.log基本问题都能在这里找到。启动正常后连接数据库/usr/local/mysql/bin/mysql -uroot -p --socket/data/mysql/data/mysql.sock登录后立即修改root密码并且把root默认只允许本机登录减小暴露面ALTER USER rootlocalhost IDENTIFIED BY StrongPassw0rd!; FLUSH PRIVILEGES;到这里一个干净的MySQL 8二进制实例已经跑起来了。接下来的重点是复制架构但这步之前有个机制层面的问题值得先讲清楚否则后面配置起来容易一头雾水。3. GTID和半同步复制配置前需要搞清楚的三个机制3.1 GTID到底解决了什么GTID的全称是Global Transaction Identifier格式为server_uuid:transaction_id每个事务在主库上执行时都会分配一个全局唯一编号。简单理解传统复制模式下从库要告诉主库“请把binlog文件mysql-bin.000008、偏移量512之后的日志发给我”这个坐标一旦算错复制链路就乱了。而GTID模式下从库只需要告诉主库“我目前已执行到哪些GTID集合剩下的你发给我”主从之间自动传递缺失的事务。这个机制对运维最友好的一点是change主库、change从库、重建复制链路时基本不用关心binlog文件名和position主从会自动协商。排查问题的时候也只需要对比Executed_Gtid_Set就能知道两边差多少数据。GTID开启有个硬性要求gtid_modeON必须和enforce-gtid-consistencyON同时存在否则mysqld启动时会直接报错拒绝启动。这也是为什么我在第二小节的my.cnf里把两个参数一起写进去。3.2 半同步复制的工作原理和代价异步复制模式下主库提交事务成功后直接将binlog发给从库不等待从库确认。这样一来主库刚提交的事务如果从库还没收到主库发生宕机这部分数据就会丢。全同步复制通过分布式事务让所有节点同时提交但性能和可用性都被拖累。半同步复制的做法是主库在提交事务时必须至少等待一个从库确认“已经收到binlog并写入relay log”之后才向客户端返回提交成功。这意味着正常情况下主库的数据几乎不会丢因为从库至少已经持有了binlog。MySQL 8.0.26之后半同步插件把术语从master/slave改成了source/replica插件名和状态变量都会出现新的命名同时旧名也还能用。这个细节在配置新版本时很关键网上大量旧教程还是rpl_semi_sync_master放到新版本里也能跑但如果照着官方新文档对状态变量会发现自己记的名字对不上。半同步不是没有代价。如果从库异常、网络抖动导致ACK一直不回来主库提交会被卡住直到超过rpl_semi_sync_source_timeout设置的毫秒数然后降级回异步模式继续服务。这个降级机制保证了主库不会因为从库故障而完全不可写但也意味着故障期间的提交可能丢数据。所以生产上通常会把timeout调成一个可接受的值比如1000到3000毫秒而不是一听半同步可靠就直接设为0。3.3 为什么先做GTID复制再开半同步我的建议是搭建顺序上分成两步第一步先把普通的GTID异步复制跑通让从库随时追平主库第二步再加载半同步插件并开启半同步。这么做的好处是如果最后半同步配置出了问题至少手里还有一个跑通的GTID复制环境不会急着在一团乱麻里找原因。半同步本质上是给普通的binlog复制加了一个ACK握手层GTID复制能正常工作以后再处理半同步的插件名称、参数时基本就是配置层面的事了。4. 从零搭建GTID主从复制的完整步骤4.1 主库创建复制账号在主库上执行CREATE USER repl% IDENTIFIED BY ReplPassw0rd!; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl%;MySQL 8.0默认的认证插件是caching_sha2_password如果从库那边连接时没有走SSL复制IO线程可能需要通过主库的公钥做密码交换。为了避免这个细节在第一次搭建时反复试错我习惯在主库上临时保留默认插件但在从库执行CHANGE REPLICATION SOURCE TO时带上GET_MASTER_PUBLIC_KEY1或者直接用旧插件创建账号省心。这里取决于你的安全基线不展开争论。需要特别注意的是复制账号不要加ALL PRIVILEGES只给复制相关的最小权限就足够。权限给大了一旦被扫到就是一个很大的隐患。4.2 数据初始化和GTID集合的传递因为本次是全新搭建数据量不大我直接用主库自带的mysqldump在线导出不需要锁库关键是两个选项/usr/local/mysql/bin/mysqldump \ --single-transaction \ --set-gtid-purgedON \ --all-databases \ --triggers --routines --events \ -uroot -p /tmp/all.sql--single-transaction借助InnoDB的MVCC保证一致性快照避免工具在导出中途把业务卡住--set-gtid-purgedON会在导出文件的头部生成SET GLOBAL.GTID_PURGED...语句从而把主库上已有的GTID集合“烧录”进从库。这一步是GTID复制里的关键如果全程不指定它导入从库后从库自己的gtid_executed是空的后续执行MASTER_AUTO_POSITION1时两边GTID集合对不上复制根本启动不了。把/tmp/all.sql传到从库后用mysql客户端导入/usr/local/mysql/bin/mysql -uroot -p --socket/data/mysql/data/mysql.sock /tmp/all.sql导入完成后从库上执行SHOW VARIABLES LIKE gtid_purged; SHOW VARIABLES LIKE gtid_executed;正常情况下两个变量的值都和主库导出的GTID集合一致。这一步验证很值得花时间因为后续所以复制状态的判断都基于这个集合。4.3 CHANGE REPLICATION SOURCE TO每个参数的含义如果是MySQL 8.0.23以上的版本官方更推荐CHANGE REPLICATION SOURCE TO语法旧版CHANGE MASTER TO也能用。从库上执行STOP REPLICA; CHANGE REPLICATION SOURCE TO SOURCE_HOST主库IP, SOURCE_PORT3306, SOURCE_USERrepl, SOURCE_PASSWORDReplPassw0rd!, SOURCE_AUTO_POSITION1, GET_MASTER_PUBLIC_KEY1; START REPLICA;SOURCE_AUTO_POSITION1是GTID模式的核心它让从库在连接主库时自动协商从哪个GTID位置开始拉取binlog不用再指定SOURCE_LOG_FILE和SOURCE_LOG_POS。传统复制里那套“看SHOW MASTER STATUS抄文件偏移量”的操作在GTID模式下基本退役了。GET_MASTER_PUBLIC_KEY1解决的是caching_sha2_password插件下复制账号首次连接时的公钥获取问题。如果你的复制账号用的是旧版mysql_native_password这个参数可以不要但留着也不影响。4.4 验证复制状态和数据一致性启动复制后查看状态SHOW REPLICA STATUS\G重点看几个字段是否都是正常Replica_IO_Running: YesReplica_SQL_Running: YesReplica_SQL_Running_State: Replica has read all relay log; waiting for more updatesRetrieved_Gtid_Set和Executed_Gtid_Set是否一致验证一致性最直接的办法是在主库建一个测试库写入一点数据然后等一两秒从库查询-- 主库执行 CREATE DATABASE testdb; USE testdb; CREATE TABLE t1 (id INT PRIMARY KEY, note VARCHAR(50)); INSERT INTO t1 VALUES (1, gtid-replication-ok);-- 从库执行 USE testdb; SELECT * FROM t1;能看到刚插入的记录说明GTID复制已经通了。此时你也刻意在主库写几十条数据再从库SHOW REPLICA STATUS\G观察Executed_Gtid_Set是不是持续追平主库的gtid_executed。这一步通过后后面的半同步才有意义。5. 在运行中的GTID复制上开启半同步5.1 加载插件与参数设置半同步需要主库和从库同时加载对应的插件。主库加载的是source端插件从库加载的是replica端插件。MySQL 8.0.26以上版本命令如下-- 主库 INSTALL PLUGIN rpl_semi_sync_source SONAME semisync_source.so; -- 从库 INSTALL PLUGIN rpl_semi_sync_replica SONAME semisync_replica.so;如果你用的是8.0.26之前的版本插件名是rpl_semi_sync_master和rpl_semi_sync_slave对应so文件名也会不同。版本不同插件名就不同这个没有通用性套用教程之前一定先确认自己的版本。加载完插件后在主库执行SET GLOBAL rpl_semi_sync_source_enabled 1; SET GLOBAL rpl_semi_sync_source_timeout 1000;在从库执行SET GLOBAL rpl_semi_sync_replica_enabled 1;之后需要重启一下从库的IO线程让半同步复制通道重新建立STOP REPLICA IO_THREAD; START REPLICA IO_THREAD;这里有个容易掉的坑如果提前在my.cnf里写rpl_semi_sync_source_enabledON但插件还没加载mysqld会直接报“Unknown system variable”拒绝启动。所以顺序一定要是先加载插件再设置参数。参数运行稳定后如果想持久化用SET PERSIST而不是手改配置文件SET PERSIST rpl_semi_sync_source_enabled 1; SET PERSIST rpl_semi_sync_source_timeout 1000;SET PERSIST会把值写入mysqld-auto.cnf每次启动自动加载。这条命令比手动改/etc/my.cnf更可控也不会因为手滑把配置文件改坏导致服务起不来。5.2 如何确认半同步真的生效很多人打开半同步后只看从库的SQL线程是Yes就以为成功了其实不一定。半同步真正生效的标志是主库提交事务后收到了从库的ACK确认。主库执行SHOW STATUS LIKE Rpl_semi_sync_source_status; SHOW STATUS LIKE Rpl_semi_sync_source_yes_transactions; SHOW STATUS LIKE Rpl_semi_sync_source_no_transactions;Rpl_semi_sync_source_status为ON表示半同步复制当前处于激活状态yes_transactions表示被从库确认过的事务数no_transactions表示因超时降级后未收到确认就提交的事务数。理想情况下应该看到yes持续增长no不增长。我平时验证半同步最直观的做法是“阻断IO线程再提交”。先在从库执行STOP REPLICA IO_THREAD;回到主库执行一个事务BEGIN; INSERT INTO testdb.t1 VALUES (2, semi-sync-test); COMMIT;此时由于从库的IO线程停了主库提交会卡住直到超过rpl_semi_sync_source_timeout也就是我在上面设置的1000毫秒然后事务才返回成功。没有半同步时这个commit几乎是瞬间完成的现在明显能感觉到停顿。这说明半同步的阻塞机制确实在工作。等这一秒过去后主库会把刚才的事务提交标记为一次no_transactions。接着把从库IO线程恢复START REPLICA IO_THREAD;再在主库提交一个新事务这次应该不会有明显延迟同时Rpl_semi_sync_source_yes_transactions加1。从库上也可以查SHOW STATUS LIKE Rpl_semi_sync_replica_status;Replica端状态为ON就说明整个半同步链路是双向握手成功的。整个过程做一遍比单纯看参数有没有设置成ON要可靠得多。6. 从启动失败到复制异常常见问题排查实录6.1 安装初始化阶段的三个高频报错二进装安装最常见的报错就是error while loading shared libraries: libaio.so.1。我前面强调过用ldd提前检查如果忘了这一步mysqld一旦启动就会摔在依赖上。解决方式是安装libaio和libnuma之后再跑一遍ldd确认。第二个高频报错出现在--initialize阶段错误信息类似“The designated data directory /data/mysql/data is not empty”。原因是数据目录里残留了之前的文件。MySQL对数据目录的检查非常严格非空就拒绝初始化所以要么清空目录要么换一个全新目录。清空之前务必确认这台机器上没有任何历史数据。第三个看起来奇怪但确实存在的是AppArmor限制。Ubuntu默认启用AppArmor如果系统里某个profile拦住了/usr/local/mysql/bin/mysqld的读写路径mysqld启动时会报权限错误但log里又不直接说是AppArmor。排查办法是先看aa-status确认mysqld是否被某个profile管控。正常情况下二进制包装到/usr/local/mysql不一定会触发默认profile但如果之前装过apt版MySQL残留的AppArmor配置可能会影响后续二进制包运行。处理这种问题不要贸然把整个AppArmor关闭而是先弄清楚是哪条路径被封了再针对性的做放行。6.2 半同步静默退化成异步的典型场景半同步运行一段时间后SHOW STATUS LIKE Rpl_semi_sync_source_status变成OFF说明已经降级为异步复制。常见原因是从库IO线程重启、网络闪断、或者主库在等待ACK时超时。有一次我在从库重启IO线程后发现主库的半同步状态没有自动恢复成ON随后几个事务都被标记为Rpl_semi_sync_source_no_transactions。查了半天原因是主库在超时后已经把这个“source端半同步”全局状态关掉了光重启从库IO线程没用。处理方法是SET GLOBAL rpl_semi_sync_source_enabled 1; STOP REPLICA IO_THREAD; START REPLICA IO_THREAD;两个动作配合等主库再次看到ACK后状态会恢复。这个坑的教训是半同步不是“配置一次永久生效”它本身带降级机制运维监控里必须把Rpl_semi_sync_source_status作为一条指标盯着一旦变成OFF就要收到告警而不是等业务发现写库卡顿才发现问题。6.3 复制报错后的定向处理GTID模式下最常见的复制中断原因就是SQL线程执行失败例如手工在从库改过数据导致主键冲突、或者跳过了一些正常事务导致后续事务无法应用。查看状态时Replica_SQL_Running: NoLast_Error会直接写明卡在哪个事务上。如果确认某个事务不需要应用可以按GTID精确跳过STOP REPLICA; SET GTID_NEXT主库UUID:事务ID; BEGIN; COMMIT; SET GTID_NEXTAUTOMATIC; START REPLICA;这个操作的原理是手动向从库的空事务里填入这个GTID让SQL线程认为该事务已执行从而继续往下。它只能用于能明确判断无影响的事务。如果连续报错与其一个事务一个事务跳过不如回头检查是不是有人手工改了从库数据。此外主从端的server_id重复也会导致IO线程一直接连不上状态提示会直接抓到“Found a duplicate server_id”修改从库server-id后重启IO线程即可。6.4 安全兜底与故障切换前的习惯从库在日常运行中必须开启只读这是我在搭建时一定会做的事SET GLOBAL read_only ON; SET GLOBAL super_read_only ON;super_read_only连超级用户都会限制写操作是防止有人不小心在从库执行DML的硬保护。故障切换时再临时关掉变成新主库流程上要可控得多。另外还有一个很容易被忽略的问题主从两边的server_uuid必须不同。使用mysqldump方式初始化从库不会复制auto.cnf所以一般没有这个问题但如果有人图省事直接拷贝了主库的数据目录没有删除auto.cnf就把从库启动两边UUID相同会导致GTID复制完全无法工作。判断办法很简单SHOW VARIABLES LIKE server_uuid;如果主从一致删除从库数据目录里的auto.cnf文件重启mysqld会自动生成新的UUID再重搭复制链路即可。写在最后我个人的体会是这类环境搭建最怕的不是步骤复杂而是变量叠加。第一次搭这套环境不要一上来就同时开GTID、同时开半同步那样出了错根本分不清是哪一个环节的问题。先把GTID异步复制跑通看到Executed_Gtid_Set在主从两边稳步推进再加载半同步插件最后用停IO线程的方式做一次故障演练验证整个过程可以控制在半小时内。生产环境里半同步的timeout我一般设置在1000到3000毫秒太小容易频繁降级太大会让主库提交卡顿明显。至于后续的主从切换演练GTID模式下的优势会体现得非常明显主备之间换岗基本不依赖binlog坐标值得在搭建完成后顺手练一遍。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑