分库分表不是银弹
分库分表解决的是单表容量与写入瓶颈,代价是查询能力和运维复杂度,本文记录决策顺序与实际付出的成本。
分库分表在很多团队是一种默认操作:数据量一大就分。但它换来的收益很窄,付出的代价很宽,而且代价大多在上线半年后才开始显现。
先问要不要分
分库分表真正解决的问题只有两个:单表数据量导致的索引效率下降,以及单库写入的 IOPS 瓶颈。除此之外的问题它都解决不了,甚至会恶化。
在决定分之前,这几件事的性价比更高:
- 加索引或改查询。慢查询里相当大一部分是索引缺失或者写法导致索引失效,跟数据量无关。
- 冷热分离。交易流水表里九成查询集中在最近三个月。把历史数据归档到历史库,主表立刻瘦身,成本远低于分表。
- 读写分离。读多写少的场景加从库就够了。
- 升配。听起来不体面,但一台高配实例能撑到的量往往超出预期,而且省下的是人力。
我的经验阈值:单表超过两千万行且增长稳定、或者写入已经打满 IO,才考虑分表。仅仅因为"数据量看着挺大"就分,是给自己找活干。
分片键决定生死
分片键选错基本没法补救——除非停机重新迁移全量数据。
选择原则是让高频查询都能带上分片键。交易流水表用账号分片,那么"查某账号最近流水"就是单分片查询,性能很好;但"查某商户当天全部流水"就变成了全分片扫描加内存聚合。
第二类需求不要靠分片库硬扛,应该走另一条路:同步一份到 OLAP 引擎或数仓。一份数据满足所有查询模式的想法本身就是问题的根源。
另外一定要一次性把逻辑分片数定足。我们定了 1024 个逻辑分片映射到 8 个物理库,扩容时只搬分片区间,不用重新哈希。如果直接按物理库取模,第一次扩容就要迁移全量数据。
分完之后的代价
按痛感排序:
- 跨分片查询与分页。
LIMIT 10000, 20在分片环境下需要从每个分片取一万零二十条再归并,深分页基本不可用,要改成基于游标的翻页。 - 分布式主键。自增没了,要引入雪花算法或号段模式,还要处理时钟回拨。
- 事务范围收窄。原本一个本地事务能搞定的操作变成跨库,得引入最终一致方案加对账。
- DDL 变更成本翻倍。加一个字段要在所有物理库上执行,还要考虑执行期间的版本兼容。
- 运维和排障复杂度。定位一条数据要先算它在哪个分片,值班同事的上手门槛显著提高。
第 4 条最容易被低估。分表之后我们的表结构变更从"提个单子"变成了"要走一次小型发布流程",长期拖慢的是迭代速度。
分库分表是一个正确但昂贵的手段。真正需要它的时候不要犹豫,但在此之前,先把索引、冷热分离和读写分离这些便宜的选项用尽。