面试考察点

  • 是否先用索引、归档、读写分离和硬件扩展解决问题。
  • 能否根据主要查询选择分片键。
  • 是否考虑扩容、迁移、全局唯一性和跨分片事务。

核心答案

分库分表用于单机容量、写入吞吐或维护窗口已成为明确瓶颈的场景。核心是选择能让主要请求单分片完成且分布均匀的分片键,同时设计路由、扩容、数据迁移、全局 ID 和跨分片查询方案。

它显著增加研发和运维复杂度,不应只因数据“可能变多”提前引入。

分片策略

按用户或租户哈希分片分布均匀,适合点查但扩容会移动数据;按时间或范围分片便于归档,却可能产生热点。实际系统可使用虚拟槽、一致性映射或逻辑分片降低扩容影响。

先确认是否真的需要分片

分库分表前应排除更低成本手段:修复慢 SQL 和索引、归档冷数据、拆分读模型、读写分离、提升单机规格、合理分区、异步化批处理。只有在单库写吞吐、磁盘容量、备份恢复窗口或单表维护成本有明确证据时,才进入分片设计。

发现瓶颈 -> 用真实数据压测验证 -> 先做单机优化
        -> 明确容量和增长曲线 -> 选择分片键
        -> 设计路由/迁移/查询降级 -> 灰度上线

“数据量过亿”不是充分理由。几十亿行的时间序列表可能通过归档和索引稳定运行;很小但写入极热的租户表也可能需要早期拆分。

分片键评审清单

一个候选分片键要同时回答:

  • 最主要的读写请求是否都带该键,能否单分片路由?
  • 数据与 QPS 是否均匀,头部租户或热门用户有多大?
  • 是否需要跨键 JOIN、事务、唯一约束、排序或聚合?
  • 未来增加节点时,预计移动多少数据,迁移期间怎么读写?
  • 数据删除、归档、合规擦除是否可以按该键执行?

按 tenant_id 分片常让绝大多数租户请求单分片完成,但超级租户会成为热点;按 order_id 哈希均匀,但“查询某用户所有订单”会跨分片。应从最重要、最频繁的访问模式出发,而不是从表的主键名称出发。

路由与虚拟分片

int logicalShard = hash(tenantId) & (VIRTUAL_SHARD_COUNT - 1);
DatabaseTarget target = routingTable.lookup(logicalShard);

先映射到较多逻辑槽,再由路由表映射到物理库,可以在扩容时迁移部分槽,而不改变所有业务代码的取模规则。路由表必须版本化、可缓存、可回滚,并在请求日志中记录实际 shard,方便排障。

不要把简单 id % 16 写死在多处代码。扩到 32 时映射规则变化,历史数据位置和新数据位置都会变得难以兼容。

全局 ID 与唯一约束

自增主键只在单库唯一,跨分片需要雪花 ID、号段服务、UUID 或“分片号 + 本地序列”等方案。业务唯一约束也要明确归属:若邮箱需要全局唯一,不能只在每个分片各建一个 unique index;可能需要中心索引表、独立账户域或预留映射服务。

全局 ID 不等于业务幂等键。创建订单重试时,应以请求幂等键或业务键先判断是否已创建,而不是每次生成新 ID。

扩容迁移流程

  1. 新增目标库、表、索引和校验任务。
  2. 发布同时支持旧路由和新路由的应用代码。
  3. 对迁移范围启用双写、变更日志或 CDC,确保增量不丢。
  4. 按批次回填历史数据,校验行数、校验和和关键聚合。
  5. 让读取逐步切到新位置,处理双读不一致与回退。
  6. 观察稳定后停止旧写入、保留回滚窗口,再清理旧数据。

每一步都需要幂等。双写失败时不能简单忽略,要记录待修复事件;双读若发现差异,应定义哪边是事实源。迁移过程本身往往比“设计分片算法”更决定项目成败。

跨分片查询怎么办

优先避免:把需要聚合的数据冗余到查询域,按租户/时间预聚合,或使用检索/分析系统承接。确实必须跨分片时,限制 fan-out 数量、并行超时、结果大小和排序深度;全局分页和精确总数都可能非常昂贵。

跨分片事务优先重构为单分片事务 + 可靠事件。TCC、Saga 或分布式事务只用于确实无法拆开的少数核心流程,并配套对账和人工处理。

工程设计

路由层必须支持版本化配置和灰度。扩容常采用双写或变更捕获、历史数据回迁、校验、读切换和旧链路下线;每一步都要幂等、可观测和可回滚。

常见误区

分片后 JOIN、聚合、分页和唯一约束都会跨节点放大。读写分离解决读吞吐,不解决单表写入和容量;分表也不会自动修复错误索引和低效 SQL。

高频追问与参考回答

追问:如何处理跨分片事务?

优先调整聚合边界让事务落在单分片;无法避免时根据一致性要求选择可靠消息、Saga 或分布式事务,并配套幂等、补偿和对账。

追问:范围分片和哈希分片怎么选?

范围分片利于时间归档、范围扫描和局部顺序,但热点更明显;哈希分片更均匀,点查友好,但范围查询和归档会扇出。选择取决于主查询与生命周期需求。

追问:超级租户怎么处理?

先量化它的读写、存储与访问模式。可为其单独分片、按其内部二级键再拆分,或建立独立读模型;不能让一个超级租户拖垮所有普通租户的路由策略。

追问:如何验证迁移没有漏数据?

分批校验主键数量、范围校验和、关键字段聚合和抽样明细;持续比对增量日志,双读期记录差异并重放修复,不能只看总行数相同。

总结

分库分表是容量工程而非单一中间件配置,设计必须从查询模式一直覆盖到迁移、故障和长期治理。

机制全景图

下面把「MySQL 什么时候需要分库分表?怎么设计?」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。

flowchart LR
    A["估算单库容量上限"]
    A --> B["选择分片键与路由"]
    B --> C["执行单片读写"]
    C --> D["处理跨片查询事务"]
    D --> E["扩容迁移并核对"]

完整链路:从输入到结果

沿着「估算单库容量上限 → 选择分片键与路由 → 执行单片读写 → 处理跨片查询事务 → 扩容迁移并核对」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。

1. 估算单库容量上限

分片前先证明单机在数据量、写吞吐或维护窗口上达到真实瓶颈,避免过早引入分布式复杂度。

2. 选择分片键与路由

分片键决定流量与数据分布,应兼顾高频查询路由、热点和未来迁移;低基数字段通常不适合。

3. 执行单片读写

带分片键请求可直达单片,唯一 ID、二级索引和缓存 Key 都要携带路由信息。

4. 处理跨片查询事务

跨片聚合、排序、Join 和事务需要上层合并或专门协议,成本远高于单库。

5. 扩容迁移并核对

扩容通过双写/变更日志与存量迁移完成,必须定义切读、回滚和逐行校验。

源码与实现定位

入口 阅读重点
ShardingSphere route context 路由单元与广播 SQL
information_schema.tables 各分片行数/体积偏斜

源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。

参数配置与可复现实验

shardingColumn: user_id
algorithmExpression: ds_$->{user_id % 16}.orders_$->{user_id % 32}

回放用户侧与商家侧 Top 查询,统计单片/跨片比例;制造超级用户验证热点和扩容迁移。

验证步骤与预期结果

1. 固定输入和基线

先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「跨片请求」为主基线,记录值应满足「核心路径<5%示例」;同时保存 分片数据与 QPS 偏斜、跨片请求比例,使后续变化能够回到同一时间轴比较。

2. 从实现入口确认路径

在「ShardingSphere route context」确认请求确实进入「路由单元与广播 SQL」对应的实现,再沿「information_schema.tables」观察「各分片行数/体积偏斜」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。

3. 注入本文特有的失败模式

优先复现「分片键形成超级热点」,并把单一变量逐级放大,直到「跨片请求」越过「持续>20%」。随后再分别验证「全局唯一约束无法由单片保证」和「迁移期间双写无幂等与核对」,三类故障分开执行,避免多个变量同时变化而无法归因。

4. 执行止损和根因修复

第一轮只应用「跨片读建异构索引」,确认它能控制影响范围;第二轮应用「热点租户单独路由」,验证核心链路恢复;最后落实「迁移双写带版本并核对」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。

5. 通过退出条件

实验只有同时满足三项才算通过:「跨片请求」回到「核心路径<5%示例」、「最大/平均 QPS」回到「<1.5」、「迁移差异」回到「切读前 0」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。

量化基线

指标 样例基线/口径 风险线 结论
跨片请求 核心路径<5%示例 持续>20% 分片键不匹配
最大/平均 QPS <1.5 >2 数据热点
迁移差异 切读前 0 任意非零 禁止切换

这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。

事故复盘:按用户分片后商家查询变成全库扫描

订单以 user_id 分片满足用户侧查询,却忽略商家按店铺和时间查询。请求广播所有分片后容量随分片数恶化。增加面向商家的异构索引表或事件驱动查询库,比给每个请求并行扫全库更可控。

失败模式 首要证据 第一处置动作
分片键形成超级热点 分片数据与 QPS 偏斜 跨片读建异构索引
全局唯一约束无法由单片保证 跨片请求比例 热点租户单独路由
迁移期间双写无幂等与核对 迁移积压与差异 迁移双写带版本并核对

发布与回滚检查点

  • 发布前:确认「ShardingSphere route context」对应实现和上述配置在目标版本仍然有效,并保存「跨片请求」基线。
  • 灰度中:同时观察 分片数据与 QPS 偏斜、跨片请求比例、迁移积压与差异;任一指标越过表中风险线,就停止继续扩量。
  • 回滚时:先执行「跨片读建异构索引」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
  • 发布后:至少覆盖一个完整峰值周期,确认「分片键形成超级热点」没有再次出现,才关闭变更观察窗口。

方案对比与选型

方案 更适合的场景 主要收益 代价与边界
垂直拆分 业务域边界清晰、单表未超限 隔离依赖与团队 跨域事务增加
水平分片 单表数据或写吞吐超限 容量近似水平扩展 跨片查询、事务和扩容复杂
异构查询存储 多种访问路径冲突 按查询模型优化 需要同步、延迟和对账

选型至少带上 数据规模、选择性、读写比、事务长度和峰值并发,并用上面的量化基线验证;未知数据应明确为待测假设。

设计边界与工程取舍

分库分表不是数据库优化的第一步;一旦引入,路由、全局 ID、事务、扩容、备份恢复和数据治理都必须同时设计。

工程落地遵循:正确性由约束和事务兜底,性能优化必须用执行计划与测量验证。回答时直接引用「ShardingSphere route context」、配置实验和事故数据,比复述固定模板更有说服力。