news 2026/6/6 2:13:02

Java开发必知必会的MySQL核心知识点(四)-日志与高可用架构:从单机到集群

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java开发必知必会的MySQL核心知识点(四)-日志与高可用架构:从单机到集群

前三篇我们一直在"单机 MySQL"的范围内打转——单台机器上的索引怎么建、事务怎么管、锁怎么加。这已经能让你写出正确的、高效的 SQL 了。

但真实的生产环境长这样吗?你的项目只有一台数据库服务器,几百万用户在它上面读写——如果这台机器挂了怎么办?如果数据量涨到几十亿、几百亿,单表根本装不下怎么办?

从这篇开始,我们走出单机,进入分布式世界。我们先从 MySQL 的三种日志说起——它们是理解主从复制、数据恢复、甚至上一篇事务实现原理的钥匙。


一、日志系统:数据的"黑匣子"

MySQL 有三种核心日志:redo logbinlogundo log。它们分工不同,但共同保障了数据的安全与一致性。

1.1 redo log:崩溃恢复的最后防线

一句话概括:redo log 是 InnoDB 特有的物理日志,记录了"数据页上做了什么修改",用于保证持久性

它的工作原理基于一个经典设计——WAL(Write-Ahead Logging,预写日志)

正常流程(没有 redo log): 每次 UPDATE → 找到磁盘上的数据页 → 修改 → 刷回磁盘 → 每次修改都是随机磁盘 I/O,极慢! WAL 流程: 每次 UPDATE → 修改 Buffer Pool 中的内存数据 → 写入 redo log(顺序追加写) → 后台线程定期将脏页刷回磁盘 → 顺序写比随机写快 100 倍以上!

为什么 redo log 是循环写的?redo log 是固定大小的几个文件(innodb_log_file_size),写满后会从头覆盖。它只需要保证"最近修改的数据不丢失",之前的数据只要已刷到数据文件,对应的 redo log 就可以被覆盖了。

-- 查看 redo log 配置 SHOW VARIABLES LIKE 'innodb_log_file_size'; -- 每个文件大小(默认 48MB) SHOW VARIABLES LIKE 'innodb_log_files_in_group'; -- 文件数量(默认 2 个) -- 总大小 = 48MB × 2 = 96MB(对于高写入场景可能偏小)

1.2 binlog:主从复制的信使

一句话概括:binlog 是Server 层的逻辑日志,记录了所有引起数据变化的 SQL 语句(或其行数据),主要用于主从复制和按时间点恢复。

redo log vs binlog 对比:

维度redo logbinlog
所属层InnoDB 引擎层Server 层(所有引擎共用)
记录内容物理日志("第 10 页偏移 200 处写入 xxxx")逻辑日志("UPDATE user SET name='张三' WHERE id=1")
写入方式循环写(固定大小,满了覆盖)追加写(满了切新文件,不覆盖)
用途崩溃恢复(crash-safe)主从复制、数据恢复
可读性不可直接读可用 mysqlbinlog 工具查看
binlog 的三种格式
-- 查看当前格式 SHOW VARIABLES LIKE 'binlog_format';
格式记录方式优点缺点
STATEMENT记录 SQL 语句日志量小不确定函数(NOW()、UUID())会导致主从不一致
ROW(推荐)记录每行的具体变更精确,主从绝对一致日志量大
MIXED混合,大多数用 STATEMENT折中仍是过渡方案

在生产环境中,推荐使用 ROW 格式——虽然日志量大一点,但主从一致性是底线,不能有任何妥协。

数据恢复示例
# 恢复到某个时间点 mysqlbinlog --start-datetime="2025-06-01 10:00:00" \ --stop-datetime="2025-06-01 11:00:00" \ mysql-bin.000001 | mysql -u root -p

1.3 undo log:回滚与 MVCC 的幕后英雄

一句话概括:undo log 记录了数据的"反向操作",用于事务回滚和 MVCC。

INSERT → undo log 记录 DELETE(回滚时删除这条数据即可) UPDATE → undo log 记录逆 UPDATE(回滚时把值改回旧的) DELETE → undo log 记录 INSERT(回滚时重新插入)

在 004 篇讲 MVCC 时,我们提到过"版本链"——那个链就是通过 undo log 串起来的。每一行数据的DB_ROLL_PTR指向它在 undo log 中的上一个版本,这样读事务就能回溯到适合自己的那个历史版本。


三种日志各自为战还不够——当一个事务提交时,redo log 和 binlog 必须同时成功,才能保证数据的一致。如何做到?这就是两阶段提交要解决的问题。


二、两阶段提交:redo log 和 binlog 的握手协议

2.1 为什么需要两阶段提交?

假设没有协调机制:

场景 A:先写 redo log,后写 binlog → redo log 写完后宕机,binlog 没写 → 主库通过 redo log 恢复了这笔数据 → 从库没收到 binlog,没有这笔数据 → 主从不一致! 场景 B:先写 binlog,后写 redo log → binlog 写完后宕机,redo log 没写 → 主库崩溃恢复后没有这笔数据 → 从库通过 binlog 同步了这笔数据 → 主从不一致!

2.2 两阶段提交流程

事务提交过程: 1. 写入 redo log(标记为 prepare 状态) 2. 写入 binlog 3. 将 redo log 标记为 commit 状态 崩溃恢复时的判断: ┌───────────────────────────────────┬──────────┐ │ 发现情况 │ 处理方式 │ ├───────────────────────────────────┼──────────┤ │ redo log 是 prepare + binlog 完整 │ 提交事务 │ │ redo log 是 prepare + binlog 缺失 │ 回滚事务 │ │ redo log 是 commit │ 提交事务 │ └──────────────────────────────── ──┴──────────┘

核心思想:以 binlog 的完整性为准。binlog 写了,说明这个事务应该被所有节点感知到;binlog 没写,说明这个事务还不应该"出生"。


理解了日志和事务提交机制,我们就具备了理解主从复制的基础。主从复制本质上就是 binlog 在机器间的传递。


三、主从复制与读写分离

3.1 主从复制的三个角色

┌──────────┐ binlog 推送 ┌───────────┐ │ Master │ ─────────────────→ │ Slave │ │ (主库) │ │ (从库) │ │ 写操作 │ │ 读操作 │ └──────────┘ └───────────┘ ↑ ┌───────┴───────┐ │ IO 线程 │:拉取 binlog → 写入 relay log │ SQL 线程 │:执行 relay log(重放) └───────────────┘

三个步骤

  1. Master 将所有数据变更写入 binlog。
  2. Slave 的IO 线程连接到 Master,拉取 binlog 并写入本地的 relay log(中继日志)。
  3. Slave 的SQL 线程读取 relay log,逐条执行,将 Master 上的变更重放到 Slave 上。

3.2 主从延迟:最常见的生产问题

延迟原因
  • Slave 机器配置低于 Master("小马拉大车")。
  • Slave 承接了过多读请求,CPU/IO 被打满。
  • Master 上的大事务在 Slave 上重放需要同等的时间。
  • Slave 只有单线程回放(MySQL 5.6 之前)。
解决方案
-- MySQL 5.7+ 开启基于组提交的并行复制 -- 在 Slave 上设置 STOP SLAVE SQL_THREAD; SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK'; SET GLOBAL slave_parallel_workers = 4; -- 并行线程数 START SLAVE SQL_THREAD;
  • 对于延迟特别敏感的业务(比如下单后立即查订单),直接从Master读。
  • 对于可以接受几秒延迟的场景(如列表展示),走Slave

3.3 Java 中的读写分离实现

// AbstractRoutingDataSource 是 Spring 提供的动态数据源路由基类 public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSource(String type) { CONTEXT_HOLDER.set(type); // "master" 或 "slave" } public static void clear() { CONTEXT_HOLDER.remove(); } @Override protected Object determineCurrentLookupKey() { return CONTEXT_HOLDER.get(); // Spring 在获取连接时调用此方法 } } // 通过 AOP 自动切换:标了 @ReadOnly 的方法走从库 @Aspect @Component public class DataSourceAspect { @Before("@annotation(com.example.ReadOnly)") public void switchToSlave() { DynamicDataSource.setDataSource("slave"); } @After("@annotation(com.example.ReadOnly)") public void clearDataSource() { DynamicDataSource.clear(); } } // 业务代码中使用 @Service public class OrderService { @ReadOnly public List<Order> listOrders(Long userId) { return orderMapper.selectByUserId(userId); // 自动走从库读 } // 不加注解,默认走主库写 public void createOrder(Order order) { orderMapper.insert(order); } }

3.4 几种常见的复制架构

一主一从 → 最简单的结构,适合小项目 一主多从 → 读多写少场景,多从库分担读压力 双主互备 → 两台都是 Master,高可用,但需处理数据冲突 级联复制 → Master→Slave1→Slave2,减轻 Master 的 IO 压力

主从复制解决了"读"的扩展问题,但如果数据量继续增长,单表数据大到 B+Tree 层级过高、磁盘 IO 成为瓶颈时,唯一的选择就是分库分表


四、分库分表:当单表撑不住的时候

4.1 什么时候该出手?

重要提醒:分库分表是最后的手段,不是第一个手段。在决定分库分表之前,请先确认以下优化都已经做完了:索引优化、SQL 优化、缓存(Redis)、读写分离。

信号阈值参考
单表数据量超过2000 万行后查询性能显著下降(视硬件和索引而定)
磁盘空间单库接近磁盘容量上限
连接数单库连接数超过 2000(MySQL 单库建议上限)
写入瓶颈单库写入 QPS 达到磁盘瓶颈

4.2 垂直拆分 vs 水平拆分

垂直分库(按业务): 原来的大库 ──→ 用户库(user 相关表) → 订单库(order 相关表) → 商品库(product 相关表) → 每个库的表结构不同,拆的是"业务维度" 水平分表(按数据行): user 表 5000W 行 → user_0(id % 4 = 0) → user_1(id % 4 = 1) → user_2(id % 4 = 2) → user_3(id % 4 = 3) → 每个表结构完全一样,拆的是"数据行维度"

4.3 三种分片策略

// 1. 取模分片 —— 数据均匀,扩容需迁移 int tableIndex = userId % 4; // 0, 1, 2, 3 // 2. 范围分片 —— 扩容方便,但可能有热点 if (userId < 10_000_000) tableName = "user_0"; else if (userId < 20_000_000) tableName = "user_1"; else tableName = "user_2"; // 3. 一致性哈希 —— 扩容时迁移量最少 // 将分片节点和数据 key 都映射到 hash 环上 // 数据被顺时针方向第一个节点管理 // 扩容时只需迁移少部分数据

4.4 分库分表中间件选择

中间件类型选型建议
ShardingSphere-JDBC客户端 SDKJava 生态首选,Apache 顶级项目,无独立部署
ShardingSphere-Proxy代理层需要独立部署,支持异构语言
MyCat代理层老牌方案,社区活跃度不如 ShardingSphere

对于 Java 项目,ShardingSphere-JDBC是最自然的选择——它是一个 jar 包,直接集成在你的 Spring Boot 项目里,不需要额外的服务器。

4.5 ShardingSphere-JDBC 配置示例

spring: shardingsphere: datasource: names: ds0, ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/db_0 ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/db_1 rules: sharding: tables: t_order: actual-data-nodes: ds$->{0..1}.t_order_$->{0..1} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db-inline table-strategy: standard: sharding-column: order_id sharding-algorithm-name: tbl-inline sharding-algorithms: db-inline: type: INLINE props: algorithm-expression: ds$->{user_id % 2} tbl-inline: type: INLINE props: algorithm-expression: t_order_$->{order_id % 2}

4.6 分库分表后的五大新挑战

分库分表不是一劳永逸的,它会带来新的问题:

挑战解决方案
分布式 ID雪花算法(Snowflake),不能再用自增主键
跨分片查询避免跨分片 ORDER BY / GROUP BY,或使用中间件聚合
跨分片 JOIN字段冗余 + 应用层组装,或换用 NoSQL
分布式事务Seata(AT/TCC 模式),或接受最终一致性
平滑扩容一致性哈希 + 双写 + 渐进式数据迁移

分库分表是一把双刃剑。它能解决海量数据的存储和查询问题,但也极大增加了系统复杂度。没有到真正的瓶颈之前,不要为了"架构好看"而做分库分表。


本篇回顾

学完这一篇,你应该能回答:

  • redo log 和 binlog 分别是什么?什么用?
  • 为什么需要两阶段提交?怎么保证一致性?
  • 主从复制是怎么实现的?主从延迟怎么解决?
  • Java 项目中如何实现读写分离?
  • 什么时候需要分库分表?垂直拆分和水平拆分的区别?
  • ShardingSphere-JDBC 怎么配置?分片策略有哪几种?
  • 分库分表后会带来哪些新挑战?

学到这里,你已经覆盖了 MySQL 从单机原理到分布式架构的完整知识链。下一篇是本系列的收官之作——我们将回到 Java 代码层面,讲 MyBatis-Plus 的正确用法、慢 SQL 排查技巧、阿里巴巴开发规范,以及 12 道高频面试题的精讲。


【上一篇:Java开发必知必会的MySQL核心知识点(三)-深入理解:事务、锁与 MVCC

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 2:11:51

SQL数据定义实战代码详解:手把手搭建你的第一个数据库

在数据库的学习旅程中&#xff0c;理论的讲解固然重要&#xff0c;但若能配合上一行行真实可见、可以亲手敲下的代码&#xff0c;那种学习的效果便会大不相同。代码是最为诚实、最为具体的语言&#xff0c;它不含糊、不空泛&#xff0c;每一个字符都对应着一个明确的含义和操作…

作者头像 李华
网站建设 2026/6/6 2:11:42

Vue 项目实现关闭/刷新浏览器窗口前的离开确认提示

Vue 项目实现关闭/刷新浏览器窗口前的离开确认提示在 Vue 项目中&#xff0c;我们经常遇到这样的需求&#xff1a;用户编辑表单后未保存&#xff0c;点击关闭标签页或刷新页面时需要弹出一个确认框&#xff0c;防止数据丢失。本文将结合一个实际代码片段&#xff0c;详细介绍如…

作者头像 李华
网站建设 2026/6/6 2:07:02

gprMax3.0自定义建模避坑指南:从HDF5文件生成到.in文件配置的全流程解析

gprMax3.0自定义建模避坑指南&#xff1a;从HDF5文件生成到.in文件配置的全流程解析在电磁仿真领域&#xff0c;gprMax3.0因其开源性、高效性成为探地雷达模拟的首选工具。但当我们需要模拟非标准几何体时——比如考古现场的陶罐碎片、地下管道的复杂接口或地质层的不规则断面&…

作者头像 李华
网站建设 2026/6/6 2:06:56

上海GEO优化公司哪家好?

​上海GEO优化公司哪家好&#xff1f;本土标杆认准上海汇思远创GEO2026年&#xff0c;生成式AI全面渗透商业场景&#xff0c;GEO&#xff08;生成式引擎优化&#xff09;已取代传统SEO&#xff0c;成为企业抢占AI流量、构建品牌权威的核心基建。上海作为数字营销高地&#xff0…

作者头像 李华