MySQL主从复制原理与核心概念解析(GTID/半同步)

搞后端开发的兄弟们,只要项目稍微上点规模,MySQL主从复制这关肯定躲不过。换个角度看,主从复制就是让一台MySQL服务器(主库,Master)的数据,自动同步到另外一台或多台MySQL服务器(从库,Slave)上。这事儿听起来简单,但里面的门道不少,尤其是现在都2024年了,咱们得用点新东西,别再抱着老掉牙的binlog+position那套不放了。

咱们先聊聊Binlog。这是主从复制的基石,主库上所有的数据变更(增删改)都会记录在这个二进制日志里。从库呢,就派一个“搬运工”(I/O Thread)去主库拉取这些日志,存到自己的“中转站”(Relay Log,中继日志)里,然后再派一个“干活儿的”(SQL Thread)把中继日志里的命令执行一遍。这一套流程下来,数据就同步了。

不过,传统的基于文件位置的复制有个大坑:一旦主库宕机,你要把从库提升为主库,或者给主库挂个新从库,你得手动去算那个master_log_filemaster_log_pos,稍微算错一点,数据就全乱了。这时候,GTID(全局事务标识符) 就是你的救星了。

GTID是MySQL 5.6以后引入的,到了MySQL 8.4 LTS(这可是2024年4月刚发布的长期支持版,稳得一批)里,这玩意儿已经是标配了。它的核心思想特别简单:给每一个提交的事务一个唯一的身份证号。格式长这样:source_id:transaction_id。不管这个事务在哪儿执行,这个ID都不变。从库同步的时候,只要看自己缺哪个GTID,直接去主库要就行,根本不用管具体的binlog文件名和偏移量。用了GTID,主从切换和故障恢复能省你一半的心。

再说说复制模式。默认是异步复制,主库写完binlog就告诉客户端“我搞定了”,至于从库有没有收到,它不管。这速度快,但要是主库刚写完就挂了,从库还没来得及同步,那数据就丢了。这在金融或者支付场景里绝对是事故。

为了解决这个问题,就有了半同步复制。这名字听着玄乎,其实逻辑很直白:主库提交事务的时候,得等着,直到至少有一个从库确认收到了这个binlog(注意,只是收到,不一定执行完),主库才给客户端返回成功。这虽然会增加一点点延迟,但数据安全性直接上了一个台阶。在MySQL 8.4 LTS里,半同步复制的插件已经优化得很成熟了,性能损耗比早期版本低很多。

现在的趋势是,大家都在搞云原生和弹性伸缩,MySQL的复制架构也在变。比如MySQL 9.0 Innovation已经开始尝试向量检索的同步了,虽然咱们现在用8.4 LTS做业务稳如老狗,但眼光得放长远。另外,社区里现在讨论特别火的是并行复制(MTS),也就是从库不再是一个线程傻乎乎地回放SQL,而是多线程干活。基于slave_parallel_workers参数的配置,能让你基于库或者事务依赖(逻辑时钟)来并行回放,这对解决“主从延迟”这个老大难问题非常关键。

📖 学习建议

别在生产环境用默认的异步复制裸奔,除非你的数据丢了也无所谓。建议至少开启半同步复制,并且一定要用GTID模式,这是现代MySQL架构的底线。

---

基于MySQL 8.4 LTS 配置主从复制实战(含GTID与半同步)

光说不练假把式,咱们直接上手在MySQL 8.4 LTS环境下搭一套带GTID和半同步的主从复制。假设你已经有两台干净的服务器了,IP分别是:

先说个经验之谈点,MySQL 8.4 对密码认证插件和权限管理更严格了,别用root直接远程连,咱们专门建个复制用户。

第一步:主库配置

先改主库的配置文件 my.cnf(或者 mysqld.cnf,看你的系统)。

[mysqld] # 基础配置 server-id = 1 log-bin = mysql-bin binlog_format = ROW # 值得留意的是,,ROW模式最安全,虽然日志大点,但能避免很多坑 # GTID 配置 gtid_mode = ON enforce_gtid_consistency = ON # 半同步复制 配置 # 8.4版本里,插件文件名可能略有不同,但加载方式一样 plugin_load_add = 'semisync_master.so' rpl_semi_sync_master_enabled = 1 rpl_semi_sync_master_timeout = 1000 # 等待从库ACK的超时时间,毫秒,超过就降级为异步 # 其他优化 binlog_row_image = FULL

改完配置重启主库:systemctl restart mysqld

进到主库命令行,创建一个给从库用的账号。打个比方,这个账号就是用来拉数据的。

-- 登录主库 mysql -u root -p -- 创建复制用户,注意 MySQL 8.4 默认认证插件是 caching_sha2_password CREATE USER 'repl_user'@'192.168.1.%' IDENTIFIED BY 'StrongPassword123!'; GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'192.168.1.%'; -- 刷新权限 FLUSH PRIVILEGES; -- 查看主库状态,记录 File 和 Position(虽然GTID不用这个,但看看心里有底) SHOW MASTER STATUS\G;

第二步:从库配置

同样,先改从库的 my.cnf

[mysqld] server-id = 2 relay-log = relay-bin # GTID 配置 gtid_mode = ON enforce_gtid_consistency = ON # 半同步复制 配置 plugin_load_add = 'semisync_slave.so' rpl_semi_sync_slave_enabled = 1 # 并行复制(MTS)优化,这在社区讨论里热度很高 slave_parallel_workers = 4 slave_parallel_type = LOGICAL_CLOCK relay_log_recovery = ON

重启从库:systemctl restart mysqld

第三步:建立主从连接

现在去从库命令行操作,这一步最关键。咱们用CHANGE REPLICATION SOURCE TO(注意,新版本MySQL把MASTER改成了SOURCE,虽然旧命令还兼容,但建议用新的)。

-- 登录从库 mysql -u root -p -- 配置主库信息 CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.1.10', SOURCE_USER='repl_user', SOURCE_PASSWORD='StrongPassword123!', SOURCE_AUTO_POSITION=1; -- 这个参数等于1,就是告诉MySQL用GTID自动找位置,不用写binlog文件和pos了 -- 启动复制 START REPLICA; -- 以前叫 START SLAVE,现在改叫 REPLICA 了,跟上时代

第四步:验证

看看效果怎么样。

-- 在从库执行 SHOW REPLICA STATUS\G

你要盯着看两个指标:

再看看半同步是不是真的生效了:

-- 在主库执行 SHOW STATUS LIKE 'Rpl_semi_sync_master_status'; -- 如果值是 ON,恭喜你,半同步搞定了。

💡 经验总结

很多人喜欢直接克隆虚拟机做从库,这在MySQL 8.4里要小心server-uuid冲突。如果两台机器的/var/lib/mysql/auto.cnf里的uuid一样,复制绝对起不来。记得检查并删除从库的这个文件(或者修改uuid),重启服务让它自动生成新的。

---

ShardingSphere与MySQL Router实现读写分离配置

主从搭好了,数据也能同步了,但问题来了:你的代码怎么知道该去主库写,还是去从库读?总不能让开发兄弟们在代码里手动判断吧?这时候就需要中间件来搞读写分离了。

现在市面上主流的方案有两个:一个是官方亲儿子 MySQL Router,轻量级,适合简单场景;另一个是国产之光 Apache ShardingSphere(现在叫 ShardingSphere-JDBC 或 Proxy),功能强大,适合复杂分库分表场景。咱们都聊聊。

MySQL Router:轻量级官方方案

MySQL Router 是MySQL官方提供的,它工作在应用和数据库之间,像个智能代理。它会自动感知主从状态(配合InnoDB Cluster或者手动配置),把写请求路由给主库,读请求轮询给从库。

MySQL 8.4 LTS的生态里,Router 依然是个好选择,特别是你不想引入太重的中间件时。

安装完 Router 后,你需要初始化配置。假设你已经装好了,直接运行:

# 引导配置,它会问你主库地址、账号密码等 mysqlrouter --bootstrap root@192.168.1.10:3306 --user=mysqlrouter

它生成配置文件后,你主要看 mysqlrouter.conf 里的 [routing:read_write][routing:read_only] 段。

[routing:read_write] bind_address = 0.0.0.0 bind_port = 6446 destinations = 192.168.1.10:3306 # 主库 routing_strategy = first-available [routing:read_only] bind_address = 0.0.0.0 bind_port = 6447 destinations = 192.168.1.11:3306 # 从库 routing_strategy = round-robin # 轮询策略,如果有多个从库就爽了

启动 Router:systemctl start mysqlrouter

这时候,你的应用程序连接配置就得改了:

打个比方,Router 帮你把端口拆开了,代码里得稍微区分一下数据源,或者配两个数据源。

ShardingSphere:功能强大的“数据库增强剂”

如果你觉得MySQL Router太简单,没法做到“同一个连接里自动识别读写语句”,或者你未来还想分库分表,那就上 ShardingSphere-JDBC。它是以Jar包的形式嵌入到你应用里的,对代码侵入极小。

咱们用 ShardingSphere-JDBC 5.x 版本举个栗子。假设你用的是 Spring Boot,配置起来非常直观。

先加依赖(Maven):

<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.4.0</version> <!-- 建议用最新稳定版 --> </dependency>

然后是核心配置文件 application.yml。这是重头戏,咱们把主从配进去:

spring: shardingsphere: datasource: names: master, slave0 master: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://192.168.1.10:3306/test_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8 username: root password: your_master_password slave0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://192.168.1.11:3306/test_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8 username: root password: your_slave_password rules: readwrite-splitting: data-sources: readwrite_ds: type: Static props: write-data-source-name: master read-data-source-names: slave0 # 负载均衡算法,这里用轮询 load-balancer-name: round_robin load-balancers: round_robin: type: ROUND_ROBIN props: sql-show: true # 开发环境建议开启,能看到SQL到底去了哪个库

配完这个,你的代码里就只需要一个数据源 readwrite_ds。ShardingSphere 会自动帮你把 select 语句扔给 slave0,把 insert/update 扔给 master。这比 MySQL Router 省事儿多了,不用在代码里维护两个连接池。

⚡ 效率提示

用 ShardingSphere 的时候,有个坑得注意:事务内的读操作默认会走主库。这是为了防止“读己之写”的延迟问题(比如你刚插入一条数据,事务还没提交,去从库读可能读不到)。如果你确定某些查询不需要这种强一致性,可以在配置里开启 spring.shardingsphere.rules.readwrite-splitting.data-sources.readwrite_ds.props.read-data-source-query-forcedly: true(具体属性名看版本文档),或者干脆把查询放到事务外面。另外,根据2024年的趋势,ShardingSphere 也在适配云原生,如果你在K8s环境里跑,它的配置会更灵活。

4. 并行复制(MTS)调优与主从延迟解决方案

其实,主从复制最让人头疼的问题就是延迟。你刚在主库写入一条数据,结果去从库查的时候竟然没有,这种感觉就像你发了工资,银行卡余额却没变一样,让人抓狂。

在 MySQL 8.4 LTS 这个版本里,虽然复制的基础已经很稳了,但默认配置往往跑不出最佳性能。如果你发现从库的 Seconds_Behind_Master 居高不下,别急着砸键盘,咱们先看看是不是并行复制(MTS)没配置好。

为什么会有延迟?

咱们得先搞清楚原因,这才能对症下药。

开启并配置并行复制 (MTS)

在 MySQL 8.4 里,我们要充分利用 slave_parallel_workers。这玩意儿就是干活的线程数。

注意:并行复制主要有两个维度,一个是 DATABASE(基于库),一个是 LOGICAL_CLOCK(基于逻辑时钟/事务依赖)。现在绝大多数互联网应用都是单库多表,所以 LOGICAL_CLOCK 才是你的真命天子。

我们需要修改从库(或者主从都要改,保证一致性)的配置文件 my.cnf,或者在运行时动态设置。

这是一个在生产环境亲测有效的配置片段:

-- 先停掉复制线程,别直接改,容易炸 STOP REPLICA; -- 注意:MySQL 8.0.22+ 推荐使用 STOP REPLICA,旧的 SLAVE 也兼容 -- 设置并行工作线程数,通常设置为 4-16 之间,别太贪心,要根据CPU核数来 SET GLOBAL slave_parallel_workers = 8; -- 核心配置:设置为 LOGICAL_CLOCK SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK'; -- 保持提交的顺序一致性,防止数据错乱,这个参数很重要 SET GLOBAL slave_preserve_commit_order = 1; -- 启动复制 START REPLICA;

如果你更喜欢改配置文件(推荐,重启后不丢失),在 my.cnf 里加上这些:

[mysqld] # 开启并行复制,8个线程干活 slave_parallel_workers = 8 # 基于逻辑时钟的并行回放 slave_parallel_type = LOGICAL_CLOCK # 保证事务提交顺序与主库一致,防止读己之写的问题 slave_preserve_commit_order = 1 # 开启 GTID,配合 MTS 效果更好,故障恢复也方便 gtid_mode = ON enforce_gtid_consistency = ON

改完之后,你可以通过 performance_schema 来监控并行复制的效果。别光看 SHOW REPLICA STATUS\G,那个太粗糙了。

-- 查看工作线程的状态,看看它们是不是真的在干活 SELECT THREAD_ID, TRANSACTION_RETRIES, LAST_ERROR_NUMBER, LAST_ERROR_TIMESTAMP FROM performance_schema.replication_applier_status_by_worker;

如果你看到好几个 Worker 的 THREAD_ID 都在忙,而不是只有一个,那就说明并行复制生效了。

解决主从延迟的硬核手段

除了开启并行复制,针对 2024 年现在的硬件环境,我还有几个🔧 实战技巧:

⚡ 效率提示:如果你发现即使开了 8 个并行线程,延迟还是很高,先别急着加到 16。先去查一下是不是主库有个 ALTER TABLE 这种 DDL 在跑。在 MySQL 8.4 里,虽然支持 ALGORITHM=INSTANT 的 DDL,但如果不是 Instant 类型的 DDL,它会阻塞所有的并行回放。这时候,把 DDL 放到业务低峰期,或者用 Online DDL 工具(如 gh-ost)才是正解。

5. 主从切换、故障恢复与2024技术趋势展望

作为一个老司机,我得告诉你,主从复制搞定了只是第一步,万一主库挂了怎么办?这时候你必须要会主从切换。虽然 MySQL 8.4 LTS 很稳定,但服务器硬件、云厂商的故障是不可控的。

手动主从切换:GTID 真好用

以前最痛苦的是什么?是找 Binlog 文件和位置(MASTER_LOG_FILEMASTER_LOG_POS)。就像你告诉快递员“我在那个路口左转第三棵树下”,这太不精确了。现在有了 GTID(全局事务标识符),切换就是一句话的事。

假设我们的主库(旧)挂了,要把从库(新)提升为主库。

场景模拟

操作步骤如下(在 192.168.1.11 上执行):

-- 1. 先停掉这个从库的复制线程 STOP REPLICA; -- 2. 重置它的主库信息(让它忘掉以前的主库) RESET REPLICA ALL; -- 3. 现在它已经是主库了,我们需要看一下它的 GTID 执行到了哪 SHOW MASTER STATUS\G

看到 Executed_Gtid_Set 了吗?这就是它的身份证。假设是 aaaa:1-100

现在,如果你还有其他从库(比如 192.168.1.12),需要让他们指向新的主库 192.168.1.11

-- 在 192.168.1.12 上执行 STOP REPLICA; -- 指向新主库,注意这里不用指定 binlog 文件和位置,只管自动定位 CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.1.11', SOURCE_USER='repl_user', SOURCE_PASSWORD='password', SOURCE_AUTO_POSITION=1; -- 核心要点:这就是 GTID 自动定位的开关 START REPLICA;

看到了吗?SOURCE_AUTO_POSITION=1 这个参数简直是神来之笔。它告诉从库:“别管以前了,看 GTID 就行,缺啥补啥”。这比以前手动算偏移量靠谱多了,完全避免了数据丢失或者重复执行。

自动化故障恢复:别再人肉运维了

手动切换虽然逻辑清晰,但真出事了,你还在睡觉呢。所以,社区里现在流行用 Orchestrator 或者 MySQL Router 来做自动切换。

Orchestrator 是个很牛的工具,它通过拓扑发现,能在主库挂掉后自动选一个数据最新的从库提升为主库,并指挥其他从库归附。不过配置起来稍微有点重。

在 2024 年,如果你用的是 MySQL 8.4 或者尝试 MySQL 9.0 Innovation,你会发现 MySQL Router 越来越强。配合 InnoDB Cluster,它能实现自动的故障转移。

2024-2026 技术趋势展望

作为一个写代码的,眼光得放长远点。现在的复制技术正在发生一些有趣的变化,咱们得了解下:

1. 原生读写分离越来越智能

以前咱们做读写分离,得在代码里写死两个数据源,或者用 ShardingSphere 这种中间件。以后呢?MySQL Router 可能会更深度地集成智能负载均衡。打个比方,以后可能连中间件都省了,直接连 Router,它自己知道哪个是主库,哪个是从库,哪个延迟低,自动帮你路由。

2. 向量复制与 AI 的融合

这个很有意思。MySQL 9.0 开始支持向量检索(Vector Index)了。这意味着什么?如果你的主库存了 AI 相关的向量数据,复制链路也必须能同步这些索引。未来的复制不仅仅是同步文本数据,还要同步高维向量数据,这对网络带宽和从库回放速度都是新挑战。

3. 云原生与弹性伸缩

现在的趋势是 Serverless 和 Kubernetes。MySQL 的复制架构正在适配云原生环境。比如,你可以通过 Operator 快速拉起一个从库,同步完数据后又自动缩容。这种“用完即走”的复制能力,是未来几年的大方向。

4. 可观测性的提升

以前查延迟,我们只能看 Seconds_Behind_Master,这玩意儿不准。未来的工具会提供更细粒度的诊断,比如具体是哪个大事务阻塞了(performance_schema 里会有更详细的事务等待图)。

🔧 实战技巧:如果你现在的项目还在用 MySQL 5.7 甚至更老的版本,听我一句劝,赶紧升级到 MySQL 8.4 LTS。别等到主库宕机了,发现没有 GTID,切换起来像考古一样痛苦。8.4 作为长期支持版,修复了很多复制相关的 Bug,特别是针对“大事务阻塞”场景的优化,这能让你在深夜少接几个报警电话。