84、数据库分库分表:水平分片、垂直分片、分片键选择、跨分片查询

说实话,分库分表这个话题,我每次跟团队新人聊的时候,都会先问一句:你真的需要分吗?

很多项目一上来就搞分片,结果业务还没跑起来,复杂度先把自己压垮了。但话说回来,当单表数据量真的到了千万甚至亿级,不分确实扛不住。今天我就把分库分表这摊事,掰开了揉碎了跟你聊聊。

核心观点:分片不是银弹,但用好了是真香。关键在于——什么时候分、怎么分、分了之后怎么查。

一、分片到底在解决什么问题?

说白了,就是一张表太大了,大到单机存不下,或者单机查询慢到用户骂娘。这时候我们把一张大表拆成多张小表,分散到不同的数据库实例上。

我遇到过最夸张的一个项目,单表3亿条记录,每次查询都要扫全表,DBA天天被业务方追着打。后来我们做了水平分片,查询时间从秒级降到了毫秒级。嗯,这就是分片的魅力。

二、水平分片 vs 垂直分片

这两种分片方式,我习惯用一个比喻来解释:

  • 水平分片:把一张大表按行切分。比如用户表,按用户ID的哈希值,把1-1000号用户放分片1,1001-2000号用户放分片2。每个分片的结构一模一样。
  • 垂直分片:把一张大表按列切分。比如用户表,把经常查询的昵称、头像放一个分片,把不常用的详细地址、身份证号放另一个分片。每个分片的结构不一样。

我在项目中见过一个经典案例:一个订单表有50多个字段,其中大部分字段只在后台报表时才用到。前台查询只需要订单号、金额、状态这几个字段。于是我们做了垂直分片,前台查询速度直接翻倍。

我的建议:优先考虑水平分片。垂直分片虽然能减少单行数据量,但跨分片join的代价往往比你想象的大。除非你明确知道哪些字段是“冷数据”,否则别轻易拆列。

三、分片键怎么选?

这是整个分片设计里最关键的决策。选错了,后面全是坑。

我曾经在一个项目中,选了用户注册时间作为分片键。结果呢?新用户全往最后一个分片里写,其他分片闲得发慌。这就是典型的数据倾斜

选分片键,我总结了三条铁律:

  1. 均匀性:数据要均匀分布到各个分片。比如用用户ID的哈希值,或者用订单ID的取模运算。
  2. 业务相关性:分片键最好是查询中最常用的条件。比如订单查询经常按用户ID查,那就用用户ID做分片键。
  3. 不可变性:分片键一旦选定,尽量不要修改。否则数据迁移会让你怀疑人生。
分片键类型 优点 缺点 适用场景
哈希取模 数据分布均匀 扩容时需重新哈希 用户ID、订单ID
范围分片 扩容方便,支持范围查询 容易数据倾斜 时间序列数据
列表分片 按地域、类别划分 分片数固定,扩展性差 多租户系统

避坑指南:我曾经见过有人用UUID作为分片键,结果因为UUID是随机的,每次插入都要跨分片路由,性能直接崩了。记住:分片键一定要能本地化路由,也就是根据分片键就能直接定位到目标分片,不需要查路由表。

四、跨分片查询——分片后的最大痛点

分片一时爽,查询火葬场。这句话虽然夸张,但确实反映了跨分片查询的难度。

你想想看,数据分散在多个分片里,你要做一个全局排序、全局聚合,或者跨分片的join,怎么办?

常见的解决方案有几种:

  • 中间件聚合:比如ShardingSphere、MyCat,它们会在中间层把各个分片的结果拉回来,再在内存里做合并排序。但数据量大的时候,内存压力很大。
  • 广播查询:把查询发到所有分片,然后汇总结果。适合低频的统计查询,不适合高频的在线查询。
  • 避免跨分片:这是最优雅的方案。设计分片键时,尽量让90%以上的查询都能落到单个分片上。比如按用户ID分片,那么查询某个用户的所有订单,就只查一个分片。

我个人的习惯是:能避免就避免,实在避免不了就用中间件,但一定要做好限流和超时控制。有一次线上事故,就是因为跨分片查询超时,导致中间件线程池被打满,整个数据库集群都挂了。

五、一张图看懂分片架构

下面这张图是我自己总结的分片核心逻辑,你看一眼就能明白整个流程:

应用层 分片中间件 分片1 用户ID 1-1000 分片2 用户ID 1001-2000 分片3 用户ID 2001-3000 分片键:用户ID 路由规则:hash(user_id) % 3 查询时,中间件根据分片键直接路由到目标分片

六、分片后的数据一致性怎么保证?

分片之后,事务就成了大问题。单库的事务在分片环境下不再适用。

我常用的方案有两种:

  • 最终一致性:通过消息队列异步同步数据。比如订单创建后,先写主分片,然后发消息到队列,其他分片异步更新。适合对实时性要求不高的场景。
  • 分布式事务:用TCC(Try-Confirm-Cancel)或者Seata这类框架。但性能开销大,我只在核心交易场景下才用。

一个小技巧:如果业务允许,尽量把需要事务保证的数据放在同一个分片里。比如一个用户的所有订单、钱包、积分,都按用户ID分片。这样单分片内的事务就够用了,根本不需要分布式事务。

七、分片扩容——迟早要面对的问题

业务增长后,分片数不够用了怎么办?

我经历过一次痛苦的扩容:原来8个分片,要扩到16个。因为用的是哈希取模,所有数据都要重新分布。那次迁移整整搞了一个周末,还出了几次数据不一致的问题。

后来我学乖了,改用一致性哈希。扩容时只需要迁移部分数据,不用全量重排。虽然实现复杂一点,但长期来看值得。

嗯,分库分表这个话题,今天就聊到这儿。记住一句话:分片是手段,不是目的。能用索引解决的,别急着分片;能用缓存解决的,也别急着分片。真到了非分不可的时候,再动手不迟。


公众号:蓝海资料掘金营,微信deep3321