08 · 分库分表与读写分离
分库分表是"最后的手段",不是"涨姿势就上"。本篇讲清它到底解决什么问题、读写分离与水平拆分怎么选、路由怎么算、以及代价清单——让"要不要分"变成可判断的问题。
速览卡
| 项 | 内容 |
|---|---|
| 一句话定位 | 数据库扩展三板斧:读写分离、垂直拆分、水平拆分,以及各自的代价 |
| 核心内容 | 什么时候该拆、路由算法、分布式 ID、拆分后失去什么 |
| 读完能回答 | 我的库该先做读写分离还是直接分表?分表后 JOIN/事务怎么办? |
| 相关板块 | DB02 索引 | DB03 事务 | C04 一致性与事务 |
一、扩展顺序:先把便宜的做完
text
① 索引与 SQL 优化(DB02) —— 几乎免费
② 读写分离(加从库) —— 扩读
③ 垂直拆分(按业务分库) —— 拆耦
④ 水平分表(按数据分片) —— 扩写重要认知:大多数系统止步于 ①~③。水平分表是最重的手术,前提是前面的手段都已用尽。
二、读写分离:加从库分担读
text
写 → 主库
读 → 从库(主从复制,可能有延迟)| 收益 | 代价 |
|---|---|
| 读水平扩展 | 主从延迟:刚写就读到旧数据 |
| 主库专注写 | 要处理"写后立即读"一致性 |
主从延迟的经典坑:下单成功后立刻查订单,查的是从库旧数据 → 前端以为下单失败。 对策:关键"写后读"走主库;可容忍延迟的读走从库。
三、水平分表:按什么分
| 拆分维度 | 路由键 | 例子 | 适合 |
|---|---|---|---|
| 取模/哈希 | 主键或业务 id | user_id % 16 | 单点查询(按 id) |
| 范围 | 时间/区间 | order_2024、order_2025 | 时间序列、归档 |
| 一致性哈希 | 任意键 | 按 key 哈希环 | 节点增减影响小 |
判断框架:先确认查询模式——大多数查询按什么键来?那个键就是分片键。 分片键错了,所有查询都要扫全部分片,等于没分。
四、分片后的三个"失去"
| 失去 | 后果 | 对策 |
|---|---|---|
| 跨分片 JOIN | 不能在 SQL 里关联 | 应用层聚合 / 冗余宽表 / 按分片键聚合 |
| 跨分片事务 | 本地事务不跨分片 | 分布式事务(见 C04),成本高,尽量规避 |
| 全局唯一主键 | 自增在每个分片会撞 | 分布式 ID(见下) |
| 模糊查询/范围跨片 | 要广播到所有分片 | 搜索用 ES(见 DB09),热点用缓存 |
分库分表后的查询要带着分片键才快;不带分片键的查询是"广播查询",代价随分片数线性增长。
五、分布式 ID:全局唯一还不撞
| 方案 | 特点 |
|---|---|
| 雪花算法(Snowflake) | 时间戳 + 机器位 + 序列号,趋势递增,常用默认 |
| UUID | 简单但无序、太长(作主键伤索引) |
| 数据库分段发号 | 每次拿一段号,简单可靠 |
为什么不能自增:多分片各自自增会撞;用 UUID 当主键会因无序导致 B+树页分裂、索引膨胀。雪花号"趋势递增"对聚簇索引最友好。
六、什么时候才真正需要水平分表
判断信号(多个同时出现才考虑):
- 单表数据量大到索引装不进内存、写入/查询延迟明显劣化
- 已经做过 SQL 优化、读写分离、垂直拆分,仍不够
- 查询模式能稳定给出分片键
- 团队能承担中间件/路由的运维成本
反信号:数据量没到千万级、查询没固定键、团队还没啃完索引——先回去做 DB01~DB07。
七、自检清单
- [ ] 知道扩展顺序:先索引,再读写分离,再垂直,最后水平
- [ ] 知道主从延迟与"写后读"对策
- [ ] 分片键按查询模式定,绝大多数查询带分片键
- [ ] 知道分片后 JOIN/事务/全局 ID 的代价与对策
- [ ] 分布式 ID 选雪花,不裸用自增/UUID
- [ ] 知道水平分表是最后手段,有明确的触发信号
八、常见误区
| 误区 | 事实 |
|---|---|
| "分表能解决所有慢查询" | 没分片键的查询变广播,更慢 |
| "自增主键到分片会撞" | 要全局唯一,用分布式 ID |
| "UUID 作主键没问题" | 无序导致索引膨胀,聚簇不友好 |
| "读写分离没代价" | 主从延迟会让"写后读"读到旧数据 |
| "分完就完事" | 应用代码、迁移、运维都要跟着改造 |
| "先上分表再说" | 没触发信号就分,纯加成本 |
九、动手练习
练习 A:选分片键
订单表要按用户查、按商家查、按时间查。你会怎么分片?说明取舍,以及"另一个维度"怎么查。
练习 B:设计分布式 ID
用雪花算法的三段结构(时间/机器/序列)设计一个 64 位 ID,并说出各占多少位、能支持多少并发。
练习 C:评审一次"该不该分"
某表 500 万行、查询都带 user_id、已做读写分离、写压力仍高。给出你的升级路径与理由。
相关文档
本区
- 02 · 索引原理与优化 —— 先确认索引已到位再谈分表
- 03 · 事务与隔离级别 —— 分片后事务边界
- 09 · 搜索引擎 ES —— 模糊/跨片查询的专用出口
其他域
- 04 · 一致性与事务边界 —— 分布式事务与最终一致
- 07 · 服务端性能与容量 —— 容量规划的先后顺序