单库单表什么时候撑不住
业务早期,一张订单表、一个用户库就够用。但随着数据量涨到几千万行、并发读写冲到几千 QPS,单库单表会依次撞上三堵墙:单表索引体积过大导致查询变慢、单机连接数与内存到顶、备份和迁移时间越来越长。这时加机器、加缓存只能缓解,真正的解法是把数据拆开,让请求落到更小的表和库上。
分库分表不是银弹,它把一个简单的数据库问题,换成了分布式数据路由、跨片查询、分布式事务、扩容迁移这一整套复杂问题。所以原则很简单:能不分就不分,先做索引、读写分离、缓存;真到了单表上亿行、单机扛不住,再上分库分表。
- 垂直拆分按业务域把不同表拆到不同库,降低单库压力。
- 水平切分同一张表按某个键拆成多片,数据被分散到各片。
- 分片键决定一条数据落在哪片,选不好会热点倾斜。
- 中间件ShardingSphere 把路由逻辑从业务代码里剥离出来。
垂直拆分与水平切分
两种拆法解决不同问题:
- 垂直拆分(按业务):把用户、订单、商品分别拆到独立的库,每个库只服务自己的业务域。它解决的是单库表太多、字段太杂、团队耦合的问题,不解决单表太大的问题。
- 水平切分(按数据行):订单表还是那张订单表,但按 user_id 或 order_id 取模,拆成 order_0、order_1 一直到 order_15,每个分片存一部分数据。它直接解决单表数据量过大的问题。
真实项目里往往两步都做:先垂直把业务拆库,再对订单这种大表做水平切分。垂直拆完之后,跨库的关联查询(比如查用户同时带出订单)要靠应用层组装或冗余字段,不能再用单库 join。
分片键怎么选
分片键是水平切分的命门,选它要同时满足三点:绝大多数查询都带这个键、数据分布均匀、尽量少出现跨片。
- 用用户类查询为主,就选
user_id——同一个用户的订单落在同一片,列表查询只打一片。 - 用订单号直接查详情,就选
order_id,但要保证订单号本身编码了分片信息,否则拿订单号查时不知道去哪片。 - 别用自增主键取模,容易把热点都压在最新那片;也别用时间做唯一分片键,新数据永远打在同一台机器上,形成热点。
常见做法是把分片信息编码进分布式 ID 里:ID 的某几位表示分片号,这样拿到 ID 就知道路由,又避免了取模带来的数据倾斜。
ShardingSphere 的两种接入方式
ShardingSphere 提供两种形态,业务方都感觉不到分片的存在:
- ShardingSphere-JDBC:一个 JDBC 驱动层的增强包,以 jar 形式嵌进应用,直接在进程内做 SQL 解析和路由。轻量、无额外部署,适合 Java 应用直连。
- ShardingSphere-Proxy:一个独立部署的代理服务,对外暴露 MySQL 协议,应用连它就像连一个普通 MySQL。多语言、多应用都能共用同一套路由规则。
选哪个看团队:单语言、追求性能用 JDBC;多语言、想集中治理用 Proxy。两者可以混用,Proxy 前面还能再挂一层负载均衡。
分片规则与路由配置
核心是告诉中间件:逻辑表叫什么、有哪些真实数据源、按什么键、用什么算法切。一段概念性配置大致如下:
# 逻辑表 order_item 落在 ds0、ds1 两个库
# 每个库内 order 表按 order_id 取模拆成 16 张
tables:
order:
actualDataNodes: ds${0..1}.order_${0..15}
tableStrategy:
standard:
shardingColumn: order_id
shardingAlgorithmName: mod_16
中间件拿到 SQL 后做三件事:解析 SQL、识别分片键、按算法把逻辑 SQL 改写成对具体真实表的物理 SQL 并并行下发。对带分片键的查询,只打命中的那一片;不带分片键的查询,则广播到所有分片,再由中间件合并结果——这就是为什么跨片查询要尽量避免。
分布式 ID 与跨片查询
分库分表后,数据库自增主键就废了,因为每张表都是独立的、自增会撞号。必须用分布式 ID:雪花算法(Snowflake)这类把时间、机器号、序列号拼进一个 64 位长整数的方案最常用,趋势递增、本地生成、不依赖中心。
跨片查询的坑要提前知道:不带分片键的列表查询要扫所有片再归并,分页、排序、count 都会变慢。应对办法:让高频查询都带分片键;必须全局检索的需求,交给 Elasticsearch 这类搜索引擎去做,而不是硬在分片库上扫。分布式事务这块可参考本站《分布式事务与一致性实战》。
扩容与数据迁移
分片数不是一次定死的。刚上线 4 片,数据涨起来要扩到 8 片,最大的麻烦是:取模算法一变,老数据的归属全乱了。直接停服迁移风险太大,主流做法是双倍扩容加双写:先把片数翻倍、老库新库双写,用后台迁移程序把老数据按新算法搬到新分片,校验一致后再切流到新路由,最后下线老库。
更省心的思路是一开始就用一致性哈希或预留足够多的逻辑片(比如一开始就切 64 个逻辑片,物理机只放 4 台),扩容时只是把某些逻辑片从一台物理机挪到另一台,逻辑片总数不变,路由规则不用重算。
实战清单
落地清单:
- 先确认瓶颈真在单表数据量,索引、缓存、读写分离都不够了再上分。
- 先垂直拆业务库,再对超大表做水平切分。
- 分片键选高频查询必带、分布均匀、尽量不跨片的键。
- 用 ShardingSphere-JDBC 或 Proxy 把路由从业务代码剥离。
- 分布式 ID 用雪花算法,自增主键一律废弃。
- 全局检索交给搜索引擎,别在分片库上硬扫。
- 扩容用双写迁移,优先预留逻辑片减少重算。
分库分表的价值在于把一台机器扛不住的数据量,摊到多台机器上线性扩展。但它换来的是一整套分布式复杂度——把分片键、分布式 ID、跨片查询这三件事想清楚,再动手引入中间件,才能真正把系统撑大而不是把问题放大。