知识全景图/ 软件工程与系统/ 如何设计一个数据库/ 如何设计分布式数据库

如何设计分布式数据库

从 MiniDB 单机撑不住开始,长出复制、选主、分片、跨分片查询、分布式事务、再平衡和运维边界

阅读定位: 这页讲“数据库为什么从一个节点变成多个节点”,不替代通用分布式系统、消息队列、缓存或大数据系统。我们只沿着 MiniDB 的数据库主线往外扩:单机故障、读压力、容量、热点、跨分片事务和跨节点查询。

一句话原则: 分布式数据库不是“把数据库部署成集群”这么简单。它是在节点会挂、网络会断、数据会分散、请求会跨节点的现实里,继续维持可用性、正确性和可解释性。
一、第一版:单机 MiniDB 正确,但机器一挂全站停

单机 MiniDB 已经有存储、索引、事务、MVCC 和优化器。但只有一个节点时,磁盘坏、进程挂、机房断电,数据库就不可用。

单机已经解决单机仍然失败下一步需要什么
事务和 WAL 能处理进程崩溃整台机器不可用时,用户仍然不能读写至少还有一个副本能接管
Buffer Pool 和索引提升性能读压力集中在一台机器读副本分担查询
优化器能选择单机计划数据太大,单机磁盘和 CPU 不够把数据分到多个节点
阶段验收: 你能说清:分布式数据库的第一动机通常不是“更高级”,而是单机的故障域、容量和吞吐边界到了。
二、第二版:先做主从复制,读能分担,但读可能变旧

最直觉的扩展是一个 primary 接收写入,把 WAL 或变更流复制给 replica。读请求可以走副本。

Primary: commit tx17 at LSN=502 send log records to Replica A / Replica B Replica: receive LSN=502 replay log expose read at replayed_lsn=502
解决了什么马上撞上的问题设计边界
读压力可以分散到副本副本可能落后主库读请求要知道能否接受旧数据
主库挂了还有副本数据谁来当新主需要选主和故障切换
复制基于 WAL 比复制整页更清晰网络中断时日志积压需要复制位点和落后监控
这一步的教训: 主从复制不是“多一份数据”就完了。副本读到的是哪个 LSN,决定了它能回答多新的问题。
三、第三版:主库挂了,必须选出唯一新主

如果 primary 挂了,两个副本都觉得自己该升主,就会出现双主写入。分布式数据库最怕的不是慢,而是两个节点各自接受写入,历史分叉。

失败场景后果需要的机制
网络分区,旧主还活着但被部分节点看不见旧主和新主同时接受写入多数派选主 / fencing
落后副本被提升为主已经提交的数据可能丢失只允许足够新的副本参选
客户端继续写旧主写入进入错误历史leader epoch / lease / 拒绝旧任期写入
Leader election rule: only node with majority votes can become leader each write carries leader_epoch nodes reject writes from stale epoch
阶段验收: 人为制造网络分区时,系统宁可让少数派不可写,也不能出现两个主同时写。
四、第四版:同步复制更安全,但提交会变慢

异步复制提交快,但主库刚 commit、日志还没到副本时主库挂了,可能丢失已返回成功的写入。于是 MiniDB 集群要决定 commit 需要多少副本确认。

复制策略优点失败代价适合场景
异步复制写入延迟低主库挂时可能丢已提交数据读扩展、可容忍少量丢失
半同步复制至少一个副本收到后返回仍可能有边界窗口折中方案
多数派提交提交有多数派证据写入延迟更高,少数派不可写强一致、高价值数据
关键取舍: 复制不是免费安全。你是在“提交延迟、可用性、丢失窗口”之间做选择。
五、第五版:数据太大,复制不能解决容量,只能分片

复制让每个节点都有一份全量数据,不能降低单节点容量压力。表太大时,必须把不同 key 范围或 hash 槽放到不同 shard。

分片方式解决什么马上暴露的问题
Hash Sharding写入更均匀,热点少一些范围查询要跨很多 shard
Range Sharding范围查询和顺序扫描友好热点范围可能压垮单 shard
Tenant / User Sharding同一用户数据尽量共址大租户会变成超级热点
手工分库分表实现简单可控迁移、扩容、跨库查询都很痛
阶段验收: 你能解释为什么“复制三份”和“分成三片”解决的是不同问题:前者偏高可用和读扩展,后者偏容量和写扩展。
六、第六版:路由层决定请求去哪一个 shard

有了分片,客户端不能再直接连某台数据库。它要根据分片元数据把 SQL 路由到对应 shard。

Shard Map: user_id hash slot 0..1023 -> shard A user_id hash slot 1024..2047 -> shard B user_id hash slot 2048..3071 -> shard C
请求类型路由方式风险
带 shard key 的点查直接路由到单 shard最好处理
不带 shard key 的查询广播到所有 shard 再合并慢且容易放大压力
跨 shard Join拉取多 shard 数据做 Join网络和中间结果可能爆炸
分片元数据变更路由表需要更新旧路由可能写错 shard
设计提醒: 分布式数据库的性能,很大程度取决于业务查询是否带着正确的分片键。
七、第七版:跨分片事务把原来的事务问题放大

单机事务只需要协调一个 WAL 和一个事务管理器。跨分片转账时,两个 shard 都要成功,否则钱可能凭空消失或增加。

transfer 100 from user A to user B Shard 1: debit A Shard 7: credit B failure: Shard 1 committed, Shard 7 failed
方案能解决什么代价
2PC参与者先 prepare,再统一 commit / abort协调者故障会阻塞,延迟高
共识复制事务日志事务决议有多数派证据实现复杂,写路径更长
业务 Saga / 补偿把长事务拆成可补偿步骤不是强原子,需要业务接受中间状态
避免跨分片事务把强一致边界放在一个 shard 内需要从业务建模阶段设计分片键
阶段验收: 你能区分:哪些操作必须跨分片强一致,哪些可以通过异步补偿或最终一致处理。
八、第八版:跨节点查询计划比单机优化器更难

单机优化器只比较本地 IO 和 CPU。分布式优化器还要估算网络传输、数据所在节点、是否能下推过滤、是否需要重分布。

优化动作为什么重要失败场景
Filter Pushdown先在 shard 内过滤,再传少量结果把全量数据拉到协调节点再过滤
Partial Aggregation每个 shard 先局部聚合把所有明细行传回来再 sum
Colocated Join同一 shard key 的数据本地 Join跨节点 shuffle 大量数据
Distributed Join必要时重分布数据中间结果和网络成本爆炸
关键变化: 分布式查询优化器要控制的不只是中间结果行数,还有数据移动距离。
九、第九版:扩容再平衡不是搬文件那么简单

新加节点后,部分 shard 要迁移过去。迁移期间仍然有读写,系统必须避免双写、漏写、读到半迁移状态。

迁移阶段风险设计处理
复制历史数据复制期间源 shard 还在写记录迁移起点 LSN,追增量日志
追平增量永远追不上热点 shard限流、短暂停写或分批切换
切换路由客户端有旧路由缓存路由版本号和过期拒绝
清理旧副本过早删除导致回滚困难确认无旧路由写入后再清理
阶段验收: 分片迁移时持续写入,最终新旧 shard 的数据必须能用 LSN 或校验和证明一致。
十、第十版:热点会击穿平均分布

Hash 分片看起来均匀,但真实业务常有超级用户、热门租户、秒杀商品、月底结账等热点。平均负载健康,不代表系统健康。

热点类型症状应对
单 key 热点一个 shard CPU / lock wait 飙升拆 key、缓存、队列化或业务限流
范围热点新写入都落在最新时间范围时间范围加 hash 后缀,或预分裂 range
大租户热点一个租户占用大量资源大租户独立 shard 或资源隔离
跨分片热点查询广播查询拖慢所有 shard物化汇总、查询改写、限制无 shard key 查询
不要误读: 分片不是把热点自动摊平。分片键选错,热点会被更隐蔽地放大。
十一、把分布式数据库设计线收起来
阶段遇到的问题长出的设计真正吸收点
单机机器挂了全站停复制副本缩小故障影响
复制副本读旧、主库故障LSN、选主、故障切换复制必须有进度和任期
异步复制提交成功也可能丢半同步 / 多数派提交安全性会换来延迟和可用性代价
全量复制容量仍在单节点分片复制和分片解决不同问题
分片请求不知道去哪路由层和 shard map分片键是数据库和业务的共同边界
跨分片修改一边成功一边失败2PC / Saga / 避免跨分片跨节点原子性很贵
跨节点查询网络搬运数据太多下推、局部聚合、分布式计划优化器要控制数据移动
十二、练习:用故障和扩容逼出设计
练习故意制造什么失败通过标准
异步复制延迟主库 commit 后立刻读副本能暴露副本 replayed_lsn,并决定是否等待
主库故障切换primary 挂掉,副本选主只出现一个新主,旧主恢复后不能继续旧任期写入
少数派网络分区3 节点切成 1 + 2少数派不可写,多数派继续服务
分片路由带 shard key 和不带 shard key 查询对比能看出单 shard 查询和广播查询成本差异
跨分片转账一个 shard prepare 后另一个 shard 失败能进入统一 commit / abort 或业务补偿路径
分片迁移迁移期间持续写入新旧 shard 通过 LSN 或校验证明一致
热点 key大量请求集中同一用户或租户能从指标定位热点 shard 和热点 key
十三、回到主线
如何设计一个数据库
如果你想回到单机 MiniDB 总主线,继续看 如何设计一个数据库
如何设计查询优化器
如果你还没理解单机计划选择、统计信息和 Join 顺序,先回到 如何设计查询优化器
如何设计数据库高可用、读写分离与故障切换
如果你关注的不是 DBMS 内部复制和选主,而是生产业务系统怎样处理主库故障、从库延迟、旧主隔离、连接池重连、切后容量和回切演练,继续看 如何设计数据库高可用、读写分离与故障切换
如何设计数据库分库分表与数据再平衡
如果你关心的是业务系统如何从单库单表迁到分库分表,怎样设计 shard key、路由版本、历史复制、增量追平、热点租户和扩容再平衡,继续看 如何设计数据库分库分表与数据再平衡
DDIA
如果你想把复制、分区、事务、批处理和流处理放进更大的数据系统框架,继续看 《DDIA》
总结: 分布式数据库不是 CAP 名词表,而是单机失败边界一步步扩出来的系统:机器会挂,于是复制;主库会挂,于是选主;异步复制会丢提交,于是有同步程度取舍;单机容量不够,于是分片;分片后请求要路由;跨分片事务很贵;跨节点查询要控制数据移动;扩容迁移和热点治理又把复杂度带回日常运维。