MySQL主从复制原理与核心概念解析(GTID/半同步)
搞后端开发的兄弟们,只要项目稍微上点规模,MySQL主从复制这关肯定躲不过。换个角度看,主从复制就是让一台MySQL服务器(主库,Master)的数据,自动同步到另外一台或多台MySQL服务器(从库,Slave)上。这事儿听起来简单,但里面的门道不少,尤其是现在都2024年了,咱们得用点新东西,别再抱着老掉牙的binlog+position那套不放了。
咱们先聊聊Binlog。这是主从复制的基石,主库上所有的数据变更(增删改)都会记录在这个二进制日志里。从库呢,就派一个“搬运工”(I/O Thread)去主库拉取这些日志,存到自己的“中转站”(Relay Log,中继日志)里,然后再派一个“干活儿的”(SQL Thread)把中继日志里的命令执行一遍。这一套流程下来,数据就同步了。
不过,传统的基于文件位置的复制有个大坑:一旦主库宕机,你要把从库提升为主库,或者给主库挂个新从库,你得手动去算那个master_log_file和master_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和半同步的主从复制。假设你已经有两台干净的服务器了,IP分别是:
192.168.1.10192.168.1.11先说个经验之谈点,MySQL 8.4 对密码认证插件和权限管理更严格了,别用root直接远程连,咱们专门建个复制用户。
先改主库的配置文件 my.cnf(或者 mysqld.cnf,看你的系统)。
改完配置重启主库:systemctl restart mysqld。
进到主库命令行,创建一个给从库用的账号。打个比方,这个账号就是用来拉数据的。
同样,先改从库的 my.cnf。
重启从库:systemctl restart mysqld。
现在去从库命令行操作,这一步最关键。咱们用CHANGE REPLICATION SOURCE TO(注意,新版本MySQL把MASTER改成了SOURCE,虽然旧命令还兼容,但建议用新的)。
看看效果怎么样。
你要盯着看两个指标:
Replica_IO_Running: YesReplica_SQL_Running: YesRetrieved_Gtid_Set 和 Executed_Gtid_Set 有值。再看看半同步是不是真的生效了:
很多人喜欢直接克隆虚拟机做从库,这在MySQL 8.4里要小心server-uuid冲突。如果两台机器的/var/lib/mysql/auto.cnf里的uuid一样,复制绝对起不来。记得检查并删除从库的这个文件(或者修改uuid),重启服务让它自动生成新的。
---
主从搭好了,数据也能同步了,但问题来了:你的代码怎么知道该去主库写,还是去从库读?总不能让开发兄弟们在代码里手动判断吧?这时候就需要中间件来搞读写分离了。
现在市面上主流的方案有两个:一个是官方亲儿子 MySQL Router,轻量级,适合简单场景;另一个是国产之光 Apache ShardingSphere(现在叫 ShardingSphere-JDBC 或 Proxy),功能强大,适合复杂分库分表场景。咱们都聊聊。
MySQL Router 是MySQL官方提供的,它工作在应用和数据库之间,像个智能代理。它会自动感知主从状态(配合InnoDB Cluster或者手动配置),把写请求路由给主库,读请求轮询给从库。
在MySQL 8.4 LTS的生态里,Router 依然是个好选择,特别是你不想引入太重的中间件时。
安装完 Router 后,你需要初始化配置。假设你已经装好了,直接运行:
它生成配置文件后,你主要看 mysqlrouter.conf 里的 [routing:read_write] 和 [routing:read_only] 段。
启动 Router:systemctl start mysqlrouter。
这时候,你的应用程序连接配置就得改了:
6446 端口。6447 端口。打个比方,Router 帮你把端口拆开了,代码里得稍微区分一下数据源,或者配两个数据源。
如果你觉得MySQL Router太简单,没法做到“同一个连接里自动识别读写语句”,或者你未来还想分库分表,那就上 ShardingSphere-JDBC。它是以Jar包的形式嵌入到你应用里的,对代码侵入极小。
咱们用 ShardingSphere-JDBC 5.x 版本举个栗子。假设你用的是 Spring Boot,配置起来非常直观。
先加依赖(Maven):
然后是核心配置文件 application.yml。这是重头戏,咱们把主从配进去:
配完这个,你的代码里就只需要一个数据源 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环境里跑,它的配置会更灵活。
其实,主从复制最让人头疼的问题就是延迟。你刚在主库写入一条数据,结果去从库查的时候竟然没有,这种感觉就像你发了工资,银行卡余额却没变一样,让人抓狂。
在 MySQL 8.4 LTS 这个版本里,虽然复制的基础已经很稳了,但默认配置往往跑不出最佳性能。如果你发现从库的 Seconds_Behind_Master 居高不下,别急着砸键盘,咱们先看看是不是并行复制(MTS)没配置好。
咱们得先搞清楚原因,这才能对症下药。
DELETE FROM log WHERE date < '2023-01-01',这种涉及到几百万行的大事务,主库可能几秒就写完 Binlog 了,但从库得一条条回放,累死累活。在 MySQL 8.4 里,我们要充分利用 slave_parallel_workers。这玩意儿就是干活的线程数。
注意:并行复制主要有两个维度,一个是 DATABASE(基于库),一个是 LOGICAL_CLOCK(基于逻辑时钟/事务依赖)。现在绝大多数互联网应用都是单库多表,所以 LOGICAL_CLOCK 才是你的真命天子。
我们需要修改从库(或者主从都要改,保证一致性)的配置文件 my.cnf,或者在运行时动态设置。
这是一个在生产环境亲测有效的配置片段:
如果你更喜欢改配置文件(推荐,重启后不丢失),在 my.cnf 里加上这些:
改完之后,你可以通过 performance_schema 来监控并行复制的效果。别光看 SHOW REPLICA STATUS\G,那个太粗糙了。
如果你看到好几个 Worker 的 THREAD_ID 都在忙,而不是只有一个,那就说明并行复制生效了。
除了开启并行复制,针对 2024 年现在的硬件环境,我还有几个🔧 实战技巧:
UPDATE 影响了 10 万行,赶紧回去改代码。把大事务拆成 LIMIT 1000 的小循环。可以这么理解,就是给从库留条活路。binlog_group_commit:在主库端,确保 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 配置合理,让主库一次提交更多的组事务,减少从库调度开销。⚡ 效率提示:如果你发现即使开了 8 个并行线程,延迟还是很高,先别急着加到 16。先去查一下是不是主库有个 ALTER TABLE 这种 DDL 在跑。在 MySQL 8.4 里,虽然支持 ALGORITHM=INSTANT 的 DDL,但如果不是 Instant 类型的 DDL,它会阻塞所有的并行回放。这时候,把 DDL 放到业务低峰期,或者用 Online DDL 工具(如 gh-ost)才是正解。
作为一个老司机,我得告诉你,主从复制搞定了只是第一步,万一主库挂了怎么办?这时候你必须要会主从切换。虽然 MySQL 8.4 LTS 很稳定,但服务器硬件、云厂商的故障是不可控的。
以前最痛苦的是什么?是找 Binlog 文件和位置(MASTER_LOG_FILE 和 MASTER_LOG_POS)。就像你告诉快递员“我在那个路口左转第三棵树下”,这太不精确了。现在有了 GTID(全局事务标识符),切换就是一句话的事。
假设我们的主库(旧)挂了,要把从库(新)提升为主库。
场景模拟:
192.168.1.10 (挂了)192.168.1.11 (原从库)操作步骤如下(在 192.168.1.11 上执行):
看到 Executed_Gtid_Set 了吗?这就是它的身份证。假设是 aaaa:1-100。
现在,如果你还有其他从库(比如 192.168.1.12),需要让他们指向新的主库 192.168.1.11。
看到了吗?SOURCE_AUTO_POSITION=1 这个参数简直是神来之笔。它告诉从库:“别管以前了,看 GTID 就行,缺啥补啥”。这比以前手动算偏移量靠谱多了,完全避免了数据丢失或者重复执行。
手动切换虽然逻辑清晰,但真出事了,你还在睡觉呢。所以,社区里现在流行用 Orchestrator 或者 MySQL Router 来做自动切换。
Orchestrator 是个很牛的工具,它通过拓扑发现,能在主库挂掉后自动选一个数据最新的从库提升为主库,并指挥其他从库归附。不过配置起来稍微有点重。
在 2024 年,如果你用的是 MySQL 8.4 或者尝试 MySQL 9.0 Innovation,你会发现 MySQL Router 越来越强。配合 InnoDB Cluster,它能实现自动的故障转移。
作为一个写代码的,眼光得放长远点。现在的复制技术正在发生一些有趣的变化,咱们得了解下:
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,特别是针对“大事务阻塞”场景的优化,这能让你在深夜少接几个报警电话。