先回答要不要拆
老王盯上订单表破亿了,提了一个「先拆为敬」的方案,被我按住了。分库分表是架构的不可逆手术:跨库 join 没了、跨库事务复杂十倍、分页要聚合……先过三道门槛再谈:
- 容量:真的到瓶颈了吗——先归档历史数据(冷热分离)、先砍索引和宽表,B+ 树 3-4 层装下几十亿行(第 3 篇),纯容量焦虑多数是错觉;
- 性能:优化空间榨干了吗——本系列前 18 篇的套路用尽了吗?读流量用从库扛了吗?缓存上了吗(Redis 系列全套)?
- 写入:单库写限制真的顶到了吗——读写分离解决读,写瓶颈才是分库分表的主场(单机写入 TPS 长期顶在硬件上限、DDL 都做不动,才叫顶到)。
结论:读瓶颈优先从库 + 缓存 + 归档,写瓶颈才考虑分片。老王最后只做了归档和读写分离,性能达标,手术取消。
真要拆:垂直与水平
| 方向 | 做法 | 解决什么 |
|---|---|---|
| 垂直分库 | 按业务域拆:订单库、商品库、用户库 | 业务解耦、单实例写压力分摊 |
| 垂直分表 | 冷热字段拆:高频小字段留主表,大字段进扩展表 | 行变小、缓存友好(第 17 篇的延伸) |
| 水平分库分表 | 同结构表按行拆到 N 库 N 表 | 单表容量与写入吞吐的终极方案 |
通常路径是垂直先行(微服务化顺手就做了),水平殿后。水平分片定下后扩容极痛,分片数一步到位取 2 的幂(16 库 × 64 表起步再按需启用),避免二次翻倍迁移。
第一决策:sharding key
分片键决定数据落位,原则只有一条:绝大多数查询都带得上它。订单业务 90% 的查询按 user_id 走(我的订单),分片键选 user_id,一查一个准。麻烦的是另外 10%:
- 按订单号查:订单号里「埋」进 user_id 的分片基因(比如取 user_id 的后几位嵌进订单号),从订单号就能算出落位,不用广播;
- 商家侧查询:按商家聚合的查询天然跨片——把商家维度同步一份到 ES 或宽表,别在分片库上硬扛;
- 数据倾斜:超级大客户一户数据顶一万户,简单哈希会被打歪——热点户单独分片或二次拆分。
连锁决策:分布式 ID
分表后各表自增主键必然冲突,全局 ID 得自己发:雪花算法(时间戳 + 机器 + 序列,趋势递增,符合第 3 篇的 B+ 树友好要求,注意时钟回拨处理)、号段模式(DB 批量发号段,应用内存派发)、或直接用 Redis INCR(Redis 系列讲过的原子计数,注意持久化与宕机回洞)。UUID 别用——随机主键的老账第 17 篇已经算过。
拆完之后,失去的和得到的
- 失去跨库 JOIN:改应用层组装或字段冗余(下单时冗余商品快照);
- 失去本地事务:同分片内还能用,跨分片上柔性事务(消息最终一致 / TCC),按第 10 篇思路保证 binlog 与消息的可靠投递;
- 聚合查询要归拢:count、sum 跨片汇总,报表流量请去数仓或 ES;
- 得到的是:容量与写入近乎线性扩展,每片小表 DDL、备份、优化全面变轻。
工具层面 ShardingSphere-JDBC(应用内嵌入)与 ShardingSphere-Proxy(代理层)是开源主流,和第 13 篇读写分离的路由选型逻辑一脉相承。最后别忘了:拆分当天就要规划数据迁移与双写校验方案,上线的不是分片,是迁移流程。
写路径的架构题答完,还剩最后一道安全题:数据丢了怎么办?备份恢复与误删演练,下一篇。
咖啡凉了,记得趁热喝。
评论 (0)