首页 / 知识库 / python服务端进阶 / 数据库优化-索引与分库分表

08 - 分库分表带来的新问题

分库分表解决了「装不下、扛不住」,但代价是引入一堆原来单库时代根本不存在的新问题。这一章把这些问题逐个讲清楚——理解它们,你才明白为什么说分库分表是「最后的重武器」。

一句话先记住:数据一旦散到多个库多张表,「主键、查询、事务」这三件原本理所当然的事,全都变难了。


8.1 问题一:主键冲突 —— 分布式 ID

单库时,主键用自增 ID(1、2、3……)就行。多个库/表各自自增,就会冲突

user_0 自增:1, 2, 3, ...
user_1 自增:1, 2, 3, ...   ← 又是 1、2、3,和 user_0 撞了!

所以分库分表后,需要一个全局唯一的 ID 生成方案

方案思路特点
UUID随机生成一长串唯一字符串简单、绝对不重复;但太长、无序,做索引/主键性能差
号段模式从一个中心表批量取一段 ID(如一次取 1000 个)到本地用高效、趋势递增;依赖一个发号库
雪花算法(Snowflake)用「时间戳+机器号+序列号」拼成一个 64 位整数趋势递增、有序、高性能,最常用

雪花算法直觉

一个 64 位的 long,拆成几段:
  [ 时间戳(毫秒) ][ 机器/数据中心编号 ][ 同一毫秒内的序列号 ]

· 时间戳在高位 → 生成的 ID 整体随时间递增(对索引友好)
· 机器编号 → 保证不同机器不冲突
· 序列号 → 同一台机器同一毫秒内也能生成多个不重复

记住结论:分库分表后,别再用数据库自增主键,改用分布式 ID(雪花算法最常见)


8.2 问题二:跨库查询 —— JOIN、分页、聚合都变难

数据散到多个库后,很多原本一句 SQL 的事,做不了了:

跨库 JOIN 做不了

用户库有 users,订单库有 orders,分在两台机器:
  ❌ 没法直接 SELECT ... FROM users JOIN orders(不在一个库,JOIN 不了)

常见解法:
  · 应用层分别查两个库,再在代码里"拼"起来
  · 冗余字段(订单表里直接冗余存用户名,就不用 JOIN 了)
  · 用数据同步 + 宽表/ES 专门做联合查询

跨分片分页很坑

要查"所有订单按时间倒序的第 100~110 条",数据分在 4 个分片:
  ❌ 不能只从某个分片取 → 因为全局的第 100 条,可能来自任意分片

朴素解法(很费):
  每个分片都取前 110 条 → 汇总 4×110=440 条 → 重新排序 → 取第 100~110 条
  → 分片越多、翻页越深,代价越大

优化:尽量用游标分页、限制翻页深度、或把结果放 ES

跨分片聚合(COUNT/SUM)

"统计所有用户总数" → 得问遍每个分片各自 COUNT,再把结果加起来

核心认知:分库分表后,只有「带分片键」的查询才快。不带分片键的查询(跨库 JOIN、全局分页、全局聚合)都变成了「问遍所有分片再合并」,很慢。这也呼应了上一章「分片键要贴高频查询维度」。


8.3 问题三:分布式事务 —— 最头疼的

单库时,一个 BEGIN...COMMIT 就能保证「要么全成、要么全败」。数据散到多个库后,一个事务要跨多个库,本地事务管不了了

转账:从"账户库A的账户1"扣钱,给"账户库B的账户2"加钱
  → 两个操作在两个不同的库
  → 库A 提交成功,库B 却失败了怎么办?
  → 单库的事务保证不了跨库的一致性

这就是分布式事务问题。常见思路(都很复杂,了解概念即可):

方案思路特点
2PC(两阶段提交)先都「准备」,协调者确认后再一起「提交」强一致,但慢、有阻塞、协调者单点风险
TCCTry(预留)- Confirm(确认)- Cancel(回滚),业务自己实现补偿灵活,但每个操作要写三份逻辑,侵入性强
本地消息表 / 最终一致靠消息队列 + 重试,保证「最终」一致(不追求实时强一致)常用、务实,允许短暂不一致

务实的做法:能不用分布式事务就不用。设计时尽量让「一个事务涉及的数据落在同一个分片」(比如同一用户的数据都在一个分片,转账在同库内完成)。实在跨库,多数业务用「最终一致」而非强一致。


8.4 问题四:扩容与数据迁移

一开始分了 4 个分片,用 hash % 4。数据涨上来,4 个不够了,要扩到 8 个:
  → hash % 8 的结果和 hash % 4 完全不同
  → 几乎所有数据都要重新计算位置、搬家 → 极其痛苦、风险高

缓解办法:
  · 一致性哈希(扩容时只影响一小部分数据)
  · 预分片:一开始就分成很多逻辑片(如 1024 个),
    物理上先放少数机器,扩容时只是「把一些逻辑片搬到新机器」,不用重算
  · 按范围/时间分片:加新分片即可,老数据不用动

前瞻性:分片方案要在一开始就考虑好未来扩容,否则后期扩容是场噩梦。


8.5 一张图总结代价

                 分库分表

    ┌───────┬───────┼───────┬────────┐
    ▼       ▼       ▼       ▼        ▼
  主键冲突  跨库查询  分布式事务  扩容迁移  运维复杂
    │       │       │       │        │
 分布式ID  冗余/ES  最终一致  预分片/  监控/备份
 (雪花)    /应用拼   /少跨库   一致性哈希 都翻倍

看着这张图,你就理解那句忠告了:前面所有招能解决,就绝不分库分表。 每一个新问题,都是实打实的开发和运维成本。


本章小结

新问题根源常见对策
主键冲突多表各自自增会撞分布式 ID(雪花算法)
跨库查询数据分散,JOIN/分页/聚合难冗余字段、ES、应用层拼装、尽量带分片键
分布式事务事务跨多库,本地事务管不了尽量同片内事务、最终一致、TCC/2PC
扩容迁移加分片导致数据要搬家预分片、一致性哈希、范围/时间分片

下一章预告:整个系列讲完,最后用一张「数据库优化决策树」,把从索引到分库分表的完整优先级路径串起来。


上一章 ← 07 - 怎么分:拆分策略 | 下一章 → 09 - 总结:数据库优化决策树