Skip to content

06.2 - 读写分离与分库分表

定位:读写分离解决"读压力",分库分表解决"数据量增长"——理解两者的适用场景和代价是架构设计的关键 面试高频度:⭐⭐⭐⭐ 考查方式:读写分离的延迟问题、分片键选择、跨分片查询、分布式事务


一、这是什么?为什么需要它?

是什么

  • 读写分离:主库处理写操作(INSERT/UPDATE/DELETE),从库处理读操作(SELECT)
  • 分库分表:当单库/单表数据量过大时,拆分为多个数据库/表

为什么需要扩展?

数据库的扩展困境:

  • 单表数据量 > 1 千万行 → 索引层高增加,写入变慢
  • 单库 QPS > 5K → Buffer Pool 竞争,IO 争抢
  • 只能纵向扩展(换更好的硬件)→ 成本高且有上限

核心 insight:读写分离解决的是"IO 争抢"问题,分库分表解决的是"数据量极限"问题。但两者都带来复杂性。在采用前,先用索引优化、缓存(Redis)、垂直拆分等更简单的手段。


二、原理拆解

2.1 读写分离

架构

应用层

    ├── 写操作 ──→ Master(主库)

    └── 读操作 ──→ Slave1, Slave2...(从库)

                    ProxySQL / ShardingSphere / 应用层路由

两大挑战

挑战 1:主从延迟导致的"读不到刚写的数据"

sql
-- 用户发表评论
INSERT INTO comments (user_id, content) VALUES (1, '好文章!');
-- 写入主库成功 ✅

-- 立即查询
SELECT * FROM comments WHERE user_id = 1 ORDER BY create_time DESC;
-- 如果查询去了从库 → 可能查不到刚插入的评论!
-- 因为 binlog 还没同步到从库

解决方案:

方案说明代价
强制读主对一致性要求高的查询走主库增加主库压力
等待同步写入后等待从库确认同步增加写入延迟
缓存写入后更新 Redis 缓存,从缓存读取增加维护复杂度

挑战 2:路由层的实现

  • 应用层:配置多个数据源,代码中决定走主/从(如 @Master @Slave 注解)
  • 中间件:ProxySQL、MyCat、ShardingSphere 等透明代理

2.2 分库分表

垂直分库 vs 水平分表

垂直分库(按业务拆分):
  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐
  │ user_db      │      │ order_db     │      │ product_db   │
  │ ┌──────────┐ │      │ ┌──────────┐ │      │ ┌──────────┐ │
  │ │ users    │ │      │ │ orders   │ │      │ │ products │ │
  │ │ user_tag │ │      │ │ order_item│ │      │ │ category │ │
  │ └──────────┘ │      │ └──────────┘ │      │ └──────────┘ │
  └──────────────┘      └──────────────┘      └──────────────┘

水平分表(按数据拆分):
  order_db
  ├── orders_0  (user_id % 4 = 0)
  ├── orders_1  (user_id % 4 = 1)
  ├── orders_2  (user_id % 4 = 2)
  └── orders_3  (user_id % 4 = 3)

分片键选择

好的分片键标准: 查询均匀分布、>90% 查询带分片键

分片键优点缺点
user_id按用户拆分最自然,用户级查询高效全局查询(如管理员查所有订单)需要扫描所有分片
order_id按订单 ID 哈希分布均匀用户查询要路由到所有分片
时间适合归档(旧数据放到慢盘)热点集中在最新分片

跨分片查询的代价:

sql
-- 按 user_id 分片
SELECT * FROM orders WHERE user_id = 42;  -- ✅ 只需查 1 个分片

-- 不带分片键
SELECT * FROM orders WHERE status = 'PAID';  -- ❌ 需要查所有 4 个分片

分库分表的常见问题

问题说明解决方案
跨分片 JOIN无法跨数据库 JOIN应用层聚合 / 宽表冗余
全局主键分片后自增 ID 冲突Snowflake、UUID、号段模式
分布式事务跨库事务需要 XA柔性事务(TCC、Saga)、最终一致性
分布式排序分页ORDER BY ... LIMIT 10 要合并结果查 N 个分片取 TopN 再合并
数据迁移增加分片数需要 rehash一致性哈希、虚拟槽位

三、面试视角

追问答案要点
读写分离的主从延迟怎么处理?刚写入的强制读主、缓存加速、等待同步
分库分表的分片键怎么选?按访问频率最高的维度分片,>90% 查询带分片键
分库分表后怎么做 JOIN?应用层聚合、宽表反范式化、字段冗余
分布式 ID 怎么做?Snowflake(时间戳+机器 ID+序列)、号段模式(数据库批量生成 ID 段)
分库分表后怎么做排序分页?查所有分片取 TopN,应用层合并排序
什么时候应该用分库分表?单表 > 1 亿行,且索引优化+缓存+读写分离都无法解决后,才考虑

📚 相关链接

  • 主从复制:**主从复制原理**
  • ← 返回 **高可用与架构索引**

Knowledge4J — Java 知识库