真实面经题目 · 原创解析

分库分表如何设计?

分库分表不是先选中间件,而是先判断单库单表在容量、吞吐、可用性、隔离性上的瓶颈,再设计一套可路由、可扩容、可治理、可回滚的数据架构。好的回答要把垂直拆分、水平拆分、分片键、路由、全局 ID、事务、查询、扩容、热点、读写分离和灰度治理串成一个完整工程方案。

出现于:阿里巴巴 · 后端开发

60 秒回答模板

回答时先说明拆分目标:解决单库单表容量、写入吞吐、索引膨胀、故障隔离和业务边界问题,而不是为了架构复杂化。然后区分垂直拆分和水平拆分:垂直拆分按业务域、访问频率、数据冷热和表职责拆库拆表;水平拆分在同一业务表数据量或写入量过大时,按稳定分片键把数据分散到多个库表。核心设计是选择分片键和路由规则,优先选择高频查询条件、基数高、分布均匀、生命周期稳定、能规避跨分片事务的字段。接着补充全局唯一 ID、跨库事务、跨分片查询、扩容迁移、热点处理、读写分离边界和治理灰度。最后强调上线要有容量评估、双写校验、灰度路由、回滚预案、监控告警和数据一致性核对。

考点 拆分目标
主线 垂直拆分
易错点 把分库分表等同于按 ID 取模,没有说明业务目标、容量…

深入解析

01

拆分目标

分库分表的第一步是明确为什么拆。常见目标包括降低单表数据量和索引高度、提升写入并发、减少单库连接和锁竞争、隔离不同业务域的故障影响、支持后续容量线性扩展。不能把分库分表当成默认优化手段,因为它会带来事务、查询、运维和一致性复杂度。面试中要先给出容量、QPS、TPS、数据增长、查询模式等判断依据,再说明只有在索引优化、归档、缓存、读写分离、硬件升级仍不足时,才进入拆分设计。

02

垂直拆分

垂直拆分解决的是业务职责和访问模式混杂的问题。可以按业务域拆库,例如订单、用户、支付、库存分别独立;也可以按表职责拆表,例如把主表、明细表、扩展属性、低频大字段、历史归档表分离。它的收益是边界清晰、故障隔离、权限和发布节奏更可控,但代价是原本单库 join 会变成服务编排或冗余字段。设计时要坚持高内聚低耦合,把强一致、高频联动的数据尽量留在同一边界内。

03

水平拆分

水平拆分解决的是同一张逻辑表数据量或写入量过大的问题。典型做法是把一张订单表按用户、商户、订单号或时间等维度拆到多个物理库表中。它要求业务访问必须能通过路由定位到少量分片,否则查询会退化成全分片扫描。水平拆分要提前设计库数量、表数量、单表容量上限、未来扩容路径和数据生命周期,不能只按当前数据量拍脑袋决定。

04

分片键选择

分片键是分库分表成败的核心。优先选择最常出现在查询条件和写入入口中的字段,且该字段要基数足够高、分布相对均匀、业务生命周期稳定、不易修改。例如订单系统常见选择 userId、buyerId、sellerId 或 orderId,但不同选择对应不同查询友好度。按 userId 拆方便查用户订单,但商家维度统计可能跨片;按 orderId 拆写入均匀,但用户订单列表需要额外索引或映射。

05

路由规则

路由规则要稳定、可解释、可灰度。常见方式有取模、范围、哈希、一致性哈希、按时间分片或复合路由。取模简单且分布均匀,但扩容迁移成本高;范围分片利于归档和范围查询,但容易产生热点;时间分片适合日志、流水、历史数据,但最新分片可能压力集中。工程上通常需要逻辑表到物理库表的映射层,由中间件或业务路由组件负责,避免业务代码散落硬编码。

06

全局唯一 ID

拆分后自增主键不能再依赖单表局部递增,否则不同分片会冲突,也难以根据 ID 判断路由。常见方案包括号段模式、雪花类 ID、数据库 sequence 服务、Redis 原子递增或专用发号服务。设计时要考虑全局唯一、趋势递增、性能、时钟回拨、可用性、机器位规划和业务可读性。如果 ID 能携带时间或分片信息,可以辅助排查和路由,但不要把过多业务含义塞进 ID,避免后续扩展受限。

07

跨库事务

分库分表后,本地事务边界被打破,跨库事务会显著增加复杂度。强一致场景可以考虑 XA 或 TCC,但成本高、性能差、对业务侵入大,一般只用于资金、库存等少数关键路径。更多业务会通过最终一致实现,例如本地消息表、可靠事件、事务消息、状态机补偿、幂等重试和对账。设计时要尽量让一次业务写入落在同一分片内,通过分片键选择减少跨片事务。

08

跨分片查询

跨分片查询是拆分后的主要痛点,包括分页、排序、聚合、模糊查询、多条件组合查询和原本依赖 join 的查询。解决思路不是简单广播所有分片,而是为核心查询建立冗余索引表、搜索引擎、宽表、汇总表或异步数据集市。分页排序要避免全分片深分页,常用游标、时间范围、二级索引或限定查询维度。聚合统计通常走离线或准实时链路,不能把所有分析查询都压回分片库。

09

扩容迁移

扩容要在设计初期预留路径。简单取模从 16 片扩到 32 片时,很多数据需要重分布,必须有迁移工具、双写或增量同步、校验、流量切换和回滚策略。常见流程是先建新分片,再全量迁移历史数据,同时消费 binlog 或业务事件补齐增量,校验通过后按租户、用户范围或百分比灰度切读,最后切写并观察。迁移期间要处理幂等、顺序、延迟、脏数据和失败重试。

10

热点与治理

热点可能来自大客户、爆款商品、热门商家、时间分片的最新区间或不均匀哈希。热点会导致某个分片 CPU、连接、锁等待或磁盘 IO 明显高于其他分片。处理方式包括更换或复合分片键、热点用户单独拆分、写入打散、冷热分离、缓存削峰、异步化、限流和预聚合。上线后还需要统一路由组件、配置中心、容量水位、慢查询治理、灰度切换、快速回滚和数据校验。

11

读写分离边界

读写分离和分库分表解决的问题不同。读写分离通过主从复制分摊读流量,适合读多写少、单库容量还能承受的场景;分库分表通过多主库或多分片分摊数据和写入压力,解决容量和写入扩展问题。读写分离会带来复制延迟、读己之写、一致性读路由等问题,但不会改变数据分布模型。实际架构中两者可以组合:每个分片内部再做主从。

易错点

  • 把分库分表等同于按 ID 取模,没有说明业务目标、容量依据和查询路径。
  • 只讲水平拆分,不讲垂直拆分、业务边界和冷热数据隔离。
  • 分片键选择只追求均匀,忽略高频查询是否能命中单分片。
  • 默认跨库 join、跨片聚合和深分页可以靠中间件透明解决。
  • 忽视全局唯一 ID,仍然使用每张表自增主键作为业务唯一标识。
  • 把读写分离当成分库分表,混淆读扩展、写扩展和容量扩展。
  • 没有扩容迁移方案,初始取模规则一旦不够用就只能停机重分布。
  • 跨库事务只说使用分布式事务,没有说明性能代价、补偿机制和幂等设计。

面试官追问

如果订单表按 userId 分片,商家后台要按 merchantId 查询订单列表,怎么设计?

可以为商家维度建立冗余索引表、搜索索引或异步宽表,把 merchantId 到订单 ID 的关系单独维护。核心交易写入仍按 userId 保持单分片事务,商家查询走二级索引链路,并通过异步一致、补偿校验和延迟提示控制体验。

取模分片从 16 个表扩到 64 个表,如何做到低感知迁移?

先建新分片和新路由版本,再做历史全量迁移,同时通过 binlog 或业务事件同步增量。校验通过后按用户范围或流量比例灰度切读,再切写,观察无异常后扩大范围。全程要有限速、幂等、回滚和数据校验。

分库分表后如何保证订单、库存、优惠券的一致性?

优先通过业务边界和分片键减少跨片事务。强一致场景可用 TCC、XA 或本地事务加锁,但成本高;更多场景用状态机、事务消息、本地消息表、幂等重试和对账补偿实现最终一致。

为什么读写分离不能解决单表数据量过大的问题?

读写分离只复制同一份数据到多个从库,分摊读流量,但每个库里的单表数据量和索引规模并没有变。单表过大导致的索引膨胀、写入压力和容量瓶颈,需要归档、分表或分库分表处理。

跨分片分页排序为什么难,如何避免全分片深分页?

因为每个分片都有局部顺序,合并全局排序需要多分片扫描、排序和截断,深分页会放大成本。可以使用游标分页、限定时间范围、二级索引表、搜索引擎或预聚合数据,避免无分片键全局扫描。