高可用与扩展:主从复制、读写分离与分库分表

各位架构师、数据库的"领航员",大家好!今天我们来聊聊数据库架构的"终极进化论"。

  • 当你的业务从"小作坊"变成"大工厂",单台数据库就像一辆满载的拖拉机,拉不动了。这时候,你需要的不是给拖拉机换个更大的轮胎(加内存、加CPU),而是把它变成一列高铁。

这就是我们今天要讲的主题:高可用与扩展------从主从复制到分库分表,看MySQL如何从"单兵作战"进化为"集团军"。

第一站:主从复制------老板与秘书的"猫鼠游戏"

底层解剖:Binlog的"三生三世"

主从复制的本质,就是日志的传输与回放

  • 老板(Master) :负责处理写操作,并把每一次修改记录在**Binlog(二进制日志)**里。
  • 秘书(Slave):负责从老板那里拷贝日志,并在自己家里重演一遍。

底层流程(三步走):

  1. Dump线程(老板的秘书):Master 为每个 Slave 启动一个 Dump 线程,专门负责发送 Binlog。
  2. I/O线程(Slave的搬运工) :Slave 连接 Master,请求日志,收到后写入本地的中继日志(Relay Log)
  3. SQL线程(Slave的执行者):读取 Relay Log,重放 SQL 操作,让 Slave 的数据与 Master 保持一致。
延迟的痛:为什么秘书总是抄得慢?

主从延迟是架构师最头疼的问题。

  • 原因
    • 单线程回放:在 MySQL 5.7 之前,Slave 的 SQL 线程是单线程的。Master 多线程写入,Slave 单线程回放,必然积压。
    • 大事务:Master 上执行一个删除 100 万行数据的操作只需几秒,Slave 回放时可能要几分钟。
  • 解决方案
    • MySQL 5.7+ 并行复制 :基于 LOGICAL_CLOCK,允许不同表或无冲突的事务并行回放。
    • 半同步复制:Master 写完日志后,必须等至少一个 Slave 收到并写入 Relay Log 才返回成功。虽然牺牲了写性能,但保证了数据不丢。

第二站:读写分离------中间件的"交通指挥"

当读请求远大于写请求时,我们需要把读流量分摊到从库。这时候,中间件(如 ShardingSphere、MyCat、ProxySQL)就登场了。

中间件的工作原理

它就像一个智能路由器,挡在应用和数据库之间。

  • SQL解析 :中间件拦截 SQL,判断是读还是写。
    • SELECT → 路由到 Slave。
    • INSERT/UPDATE/DELETE → 路由到 Master。
  • 负载均衡:如果有多个 Slave,中间件会根据策略(轮询、权重)分配读请求。
致命Bug:主从延迟导致的数据不一致

场景

  1. 用户在 Master 下单(写)。
  2. 用户立即刷新订单列表(读)。
  3. 请求被路由到 Slave,但 Slave 还没同步刚才的订单。
  4. 用户大喊:"我的订单呢?!"

架构师解法

  • 强制读主库:对于核心业务(如订单详情、支付状态),在写操作后的短时间内(如 1 秒),强制走主库。
  • 延迟双删:在更新数据时,先删缓存,再更新数据库,延时(如 500ms)后再删一次缓存。

第三站:分库分表------图书馆的"大迁徙"

当单表数据突破 500w-1000w,索引树太高,磁盘 I/O 太慢,这时候必须进行分库分表

垂直拆分:把"胖子"拆成"瘦子"
  • 垂直分库:按业务拆分。用户库、订单库、商品库分开部署。
  • 垂直分表 :把大字段(如 textblob)拆分到扩展表,主表只留核心字段。
  • 底层收益:减少单页数据量,提高缓存命中率。
水平拆分:把"长队"拆成"多窗口"

这是真正的"核武器"。将一张表的数据分散到多个库/表中。

分片策略(Sharding Strategy):

  • 取模(Hash)user_id % 4。数据分布均匀,但扩容难(需要数据迁移)。
  • 范围(Range) :按时间或 ID 范围。扩容容易,但容易产生数据热点(最新数据都在一个表)。
  • 一致性Hash:解决扩容迁移问题的进阶算法。
分布式 ID:雪花算法(Snowflake)

分库分表后,自增 ID 不能用了(不同表 ID 会重复)。
Snowflake 原理

生成一个 64 位的 Long 型 ID:

  • 1 位:符号位(固定为 0)。
  • 41 位:时间戳(毫秒级,可用 69 年)。
  • 10 位:机器 ID(区分不同节点)。
  • 12 位:序列号(同一毫秒内的自增序号)。

优点:全局唯一、趋势递增、高性能。

跨库 Join 的痛

分库后,JOIN 操作失效了。
解决方案

  • 字段冗余:在订单表里冗余用户名字段,避免 Join 用户表。
  • 应用层组装:先查订单,再批量查用户,在代码里组装(两次查询)。
  • 绑定表:将关联紧密的表(如订单主表和订单详情表)按相同规则分片,保证它们在同一个库,可以直接 Join。

总结

分库分表是架构演进的"核武器",威力大但副作用也大(运维复杂、跨库查询难)。不到万不得已(单表 500w-1000w+),不要轻易使用。

最后,送上金句

"分库分表是架构演进的'核武器',威力大但副作用也大(运维复杂、跨库查询难)。不到万不得已(单表 500w-1000w+),不要轻易使用。"

相关推荐
ZzT7 小时前
公司用 AI 筛简历,他写了个 AI 帮你挑公司
面试·aigc·ai编程
PBitW8 小时前
GPT训练我的第四天,被打惨了!!!😭😭😭
前端·javascript·面试
云技纵横12 小时前
@Transactional 到底要不要加 rollbackFor?一次数据不一致事故讲清楚
后端·面试
Moment13 小时前
牛逼,NextJs 从 16.3 开始全面拥抱 Agent Native 🥰🥰🥰
前端·后端·面试
胡萝卜术13 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
胡萝卜术14 小时前
从暴力到Z字形消元:力扣240「搜索二维矩阵II」的降维打击之路
前端·javascript·面试
洛卡卡了1 天前
我们在用 AI 写代码时,为什么建议要好好维护 AGENTS.md 呢?
面试·agent·claude
PBitW1 天前
GPT训练我的第三天,明白了应该咋说满分回答!😕😕😕
前端·javascript·面试
自由路飞2 天前
RAG 混合检索深挖:BM25 和向量分数为什么不能直接相加?
面试
未秃头的程序猿2 天前
告别"if-else地狱"!Java 21模式匹配,代码优雅了10倍
java·后端·面试