分库分表
分库分表是单库容量和写入吞吐到极限后的方案,代价巨大。开篇先说判断标准:能用索引和分区表解决的,不要上分库分表。单表数据量过 2000 万或写入 QPS 过 5000 再考虑,且业务能接受跨库查询变慢。
垂直与水平拆分
垂直拆分 vs 水平拆分
- 垂直拆分:按业务域拆库(订单、用户、商品分开),或拆大表字段(热点字段和非热点字段分开)。解决单库连接数和 IO 压力,不解决单表数据量
- 水平拆分:同一张表按分片键拆成多张表。解决单表数据量和写入吞吐,代价最大
水平拆分是面试重点。
分片键与分片算法
分片键选择是影响最深远的决策,三个标准:高基数(用户 ID 优于性别)、写均匀(避免单调递增 ID 全写一个分片)、匹配查询模式(按用户 ID 分片,同一用户的订单在同片,查历史不跨片)。
分片算法:
| 算法 | 优点 | 缺点 |
|---|---|---|
| 取模(ID % N) | 分布均匀、实现简单 | 扩容要迁移大部分数据 |
| 范围分片 | 范围查询友好 | 热点倾斜(新数据集中) |
| 一致性哈希 | 扩容只迁移相邻节点数据 | 实现复杂 |
扩容是分库分表最痛的环节。取模扩容 N 变 N’ 时大部分数据要重算位置迁移。两个解法:按 2 的幂规划容量(双倍扩容只迁移一半数据);双写迁移(新库作为从库同步,追平后切换,不停服)。
分布式 ID
分库分表后数据库自增失效(多个分片各自自增会重复)。主流方案:
| 方案 | 特点 | 适用 |
|---|---|---|
| UUID | 本地生成,无序、存储大、页分裂 | 对顺序无要求 |
| 雪花算法 | 趋势递增、高性能、无依赖 | 通用首选,注意时钟回拨 |
| 号段模式(Leaf-segment) | 全局严格递增、双号段缓冲 | 对账、订单号连续 |
| Redis INCR | 简单,依赖 Redis 高可用 | 计数器类 |
选型:通用业务用雪花,对账要求连续用号段模式。雪花算法的 64 位结构(时间戳 + 机器 ID + 序列号)和时钟回拨处理是必背细节。
跨库查询的四大难题
- 跨库 JOIN:方案是全局表(小表复制到每个分片)、冗余字段、ER 分片(强关联表按同键分片保证同库)
- 非分片键查询:映射表(记录非分片键到分片键的关系)、前缀分片(把用户特征嵌进订单 ID)、同步到 ES 做检索
- 深分页:多分片各取前 N 再合并,offset 越大越慢。带上分片键查询或业务上规避
- 分布式事务:见分布式事务篇
面试追问
- 什么时候该分库分表? 单表超 2000 万、写入 QPS 超 5000、索引和分区表已到极限。能用分区表解决的别上
- 分片键怎么选? 高基数、写均匀、匹配查询模式。按用户 ID 分片让同一用户数据同片
- 取模分片扩容怎么办? 取模扩容大部分数据要迁移。按 2 的幂规划(双倍扩容只迁一半)或双写不停服迁移
- 分布式 ID 怎么选? 通用用雪花(趋势递增、无依赖、防时钟回拨),对账连续用号段模式,无所谓顺序用 UUID
- 跨库 JOIN 怎么解决? 全局表、冗余字段、ER 分片。业务层两次查询替代 JOIN 也是常见做法