Skip to content

MySQL 高可用:主从复制与读写分离【中级】 ​


❓ 面试官:能讲讲 MySQL 主从复制的原理吗? ​

频率:🔥🔥🔥🔥🔥

💡 一句话总结(先抛结论): MySQL 主从复制的核心是 Binlog(二进制日志)。主库把所有写操作记录到 Binlog,从库的 IO 线程去拉取主库的 Binlog 并写入本地的 Relay Log(中继日志),然后从库的 SQL 线程回放 Relay Log 中的操作,从而实现数据同步。

📝 详细原理解析: 整个复制过程分为三个核心线程:

  1. 主库的 Log Dump 线程:当从库连接主库时,主库会创建一个 Log Dump 线程,专门负责将 Binlog 的内容发送给从库。
  2. 从库的 IO 线程:从库接收到主库的数据后,IO 线程将其写入到本地的 Relay Log(中继日志) 中。
  3. 从库的 SQL 线程:专门负责读取 Relay Log,并在从库的本地重新执行一遍这些 SQL(回放),从而保持数据一致。

同步模式分类:

  • 异步复制(默认):主库写完 Binlog 就算成功,直接返回给客户端。不管从库有没有收到。性能最高,但主库宕机可能丢数据。
  • 半同步复制(Semi-Sync):主库写完 Binlog 后,必须等待至少一个从库收到日志并写入 Relay Log 后,才返回给客户端。兼顾了性能和数据安全。
  • 全同步复制:主库要等所有从库都执行完事务才返回,性能极差,几乎不用。

🌟 面试加分项(实战经验): 在生产环境中,我通常会配置半同步复制。因为如果用纯异步,主库一旦突然宕机,而 Binlog 还没来得及推送到从库,直接把从库提升为主库的话,就会丢失一部分刚刚写入的数据。配置半同步复制虽然会增加一点点网络延迟(通常就几毫秒),但在金融或电商核心链路中,数据不丢是底线。


❓ 面试官:线上遇到了主从延迟,你是怎么排查和解决的? ​

频率:🔥🔥🔥🔥

💡 一句话总结(先抛结论): 主从延迟通常是因为主库并发写入太高,而从库的 SQL 线程是单线程回放跟不上导致的。解决方法主要是开启从库多线程复制(MTS),或者在业务层对强一致性要求的读请求强制走主库。

📝 详细原理解析:产生延迟的原因:

  1. 单线程回放瓶颈:在 MySQL 5.6 之前,从库的 SQL 线程只有一个。主库上千个并发线程在疯狂写入,从库只有一个线程在慢慢重放,必然延迟。
  2. 大事务:如果主库执行了一个 DELETE FROM user 删除了 500 万条数据,主库执行了 10 分钟,那从库收到日志后执行也要 10 分钟,这期间就产生了至少 10 分钟的延迟。
  3. 从库机器性能差或在从库上跑了复杂的统计报表 SQL,占用了大量 CPU 和 IO。

解决方案:

  1. 开启并行复制(MTS - Multi-Threaded Slave):MySQL 5.7+ 引入了基于逻辑时钟(组提交)的并行复制。只要在主库上是同时提交的事务(说明它们没有锁冲突),在从库上就可以多线程并发回放。这能解决 80% 的延迟问题。
  2. 避免大事务:严禁在生产环境做毫无条件的大批量 Update/Delete,必须分批次执行(比如每次 limit 5000)。
  3. 读写分离策略调整:
    • 对于"发布文章后立刻跳转到文章详情页"这种强一致性需求,业务代码里打上注解,强制去主库读。
    • 对于其他允许几十毫秒延迟的场景,再去从库读。

🌟 面试加分项(实战经验): 排查主从延迟时,我一般在从库执行 SHOW SLAVE STATUS\G。重点看两个指标:

  • Seconds_Behind_Master:代表落后主库的秒数,如果是 0 就代表没延迟。
  • 看看是 Slave_IO_Running 慢还是 Slave_SQL_Running 慢。通常 IO 线程都能跟上网络,基本都是 SQL 线程卡住了,这时候我们就会去查从库当前的进程,看是不是有什么慢查询把从库拖垮了。

❓ 面试官:你们公司的读写分离是怎么实现的? ​

频率:🔥🔥🔥

💡 一句话总结(先抛结论): 读写分离主要有两种实现方式:一种是代码层的(如 ShardingSphere-JDBC、MyBatis-Plus 动态数据源),另一种是中间件代理层的(如 Mycat、ProxySQL)。我们目前主要使用的是 ShardingSphere-JDBC。

📝 详细原理解析:

  1. 客户端直连组件(推荐,如 ShardingSphere-JDBC)

    • 原理:以 JAR 包的形式引入到 Java 应用中,重写了 JDBC 的标准接口。当程序执行 SQL 时,组件会在内存里解析 SQL,判断是 INSERT/UPDATE 还是 SELECT,自动路由到对应的数据库 URL。
    • 优点:性能极高,没有中间件的网络损耗;不需要额外部署运维。
    • 缺点:不能跨语言(只能 Java 用);升级需要重新发版。
  2. 服务端代理中间件(如 Mycat、ProxySQL)

    • 原理:单独部署一个代理服务器,伪装成一个 MySQL。Java 应用程序把代理服务器当成普通的 MySQL 连接。代理服务器自己去解析 SQL 并分发给真实的后端主从库。
    • 优点:对应用完全透明,支持任何语言。
    • 缺点:多了一层网络转发,性能有一定损耗;存在单点故障风险,需要为代理本身做高可用(如 Keepalived + HAProxy)。

🌟 面试加分项(实战经验): 使用读写分离时最容易踩坑的就是同一个事务内发生了主从切换。比如在一个事务里,我先 INSERT 了一条订单,紧接着去 SELECT 这个订单。如果框架不智能,SELECT 被路由到了从库,由于主从延迟肯定查不到数据,业务就报错了。 好在我们用的 ShardingSphere-JDBC 有一个默认机制:只要开启了事务(@Transactional),这个事务内的所有读写请求,无论是不是 SELECT,都会强制路由到主库执行,这完美避免了同一事务内的读写不一致问题。