分库分表基础:数据分片概念、垂直分库与水平分库、垂直分表与水平分表、分片键的选择策略

好,咱们正式开始聊分库分表。说实话,很多同学一听到这四个字就觉得头大,觉得是特别高深的东西。其实没那么玄乎。你想想看,数据库撑不住了,无非就是两个问题:一个是数据太多了,一个是请求太猛了。分库分表,就是针对这两个问题给出的解决方案。

我个人习惯把分库分表拆成两个维度来看:一个是「分库」,一个是「分表」。然后再各自拆成「垂直」和「水平」两个方向。这样一组合,就是四个核心概念。咱们一个一个来过。

数据分片:到底在分什么?

先说说数据分片这个概念。说白了,就是把一个大数据库里的数据,拆成多个小数据库来存。每个小数据库,就是一个「分片」。

我记得刚入行那会儿,带我的老大哥跟我说过一句话:「分片不是目的,让系统能扛住才是目的。」这句话我一直记到现在。分片之后,每个分片只承担一部分数据和一部分请求,压力自然就小了。

数据分片的核心思路,就是「分而治之」。把一个大问题拆成多个小问题,每个小问题单独解决。这个思路在计算机领域随处可见,分库分表只是其中一个应用场景。

垂直分库:按业务拆

垂直分库,说白了就是「按业务拆库」。比如你原来有一个大库,里面既有用户表、订单表,又有商品表、库存表。随着业务发展,这个库扛不住了。怎么办?

我建议的做法是:把用户相关的表放到一个库,订单相关的表放到另一个库,商品相关的表放到第三个库。这就是垂直分库。

垂直分库的核心原则:每个库只负责一个业务域,库与库之间通过接口或服务调用,而不是直接跨库join。

我在项目中遇到过这样一个坑:有个团队做垂直分库,但没做好服务隔离。订单服务直接连用户库查数据,用户服务也直接连订单库。结果一个库挂了,所有服务都跟着瘫痪。嗯,这里要注意:垂直分库之后,一定要配合微服务架构,每个库只能被对应的服务访问。

水平分库:按数据拆

垂直分库解决的是「业务耦合」的问题。但有时候,一个业务表的数据量本身就特别大,比如订单表,一年下来几亿条。这时候垂直分库也救不了你,因为所有订单数据还在一个库里。

水平分库就是干这个的。它把同一张表的数据,按照某个规则拆到多个库中。比如订单表,我可以把2023年的订单放到库A,2024年的订单放到库B。或者按用户ID取模,用户ID为奇数的去库A,偶数的去库B。

你想想看,原来一个库扛1亿条数据,现在拆成10个库,每个库只扛1000万条。压力瞬间就降下来了。

我的经验:水平分库时,分片数量最好是2的幂次方,比如8、16、32。这样后续扩容时,数据迁移的代价最小。我曾经吃过亏,一开始分了10个库,后来要扩到20个,数据迁移搞得我焦头烂额。

垂直分表:把宽表拆窄

垂直分表,就是把一张列特别多的表,拆成多张表。每张表只保留一部分字段。

举个例子,用户表可能有50个字段。其中20个是经常查询的(用户名、手机号、邮箱),另外30个是不常用的(用户偏好设置、积分详情、收货地址历史)。

我建议的做法是:把常用的20个字段放到主表,不常用的30个字段放到扩展表。查询时,默认只查主表,只有需要详细信息时才去查扩展表。

这样做的好处很明显:主表的数据行变短了,一页能存更多行,查询效率自然就上去了。而且减少了不必要的IO开销。

避坑指南:我曾经见过一个团队,把一张表拆成了七八张,每张表只有两三个字段。结果业务查询时要join七八张表,性能反而更差了。垂直分表不是拆得越细越好,适度才是关键。

水平分表:把大表拆小

水平分表和水平分库的思路类似,但范围不同。水平分表是在同一个数据库里,把一张大表拆成多张小表。比如订单表,我可以拆成order_1、order_2、order_3……一直到order_32。

什么时候用水平分表?我个人习惯是:当单表数据量超过500万行,或者单表容量超过10GB时,就开始考虑水平分表。当然这个阈值不是绝对的,要看你的业务场景和硬件配置。

水平分表和水平分库可以组合使用。比如先水平分库到8个库,每个库里再水平分表到8张表。这样总共就有64个分片。每个分片的数据量就很小了。

分片键的选择策略:最关键的决策

好了,前面讲了四种分片方式。但不管用哪种方式,都绕不开一个问题:用什么字段来做分片?这个字段就是「分片键」,也叫Sharding Key。

分片键选得好,系统跑得稳。分片键选得差,系统天天挂。这不是夸张,我在项目中见过太多因为分片键选错而翻车的案例。

分片键的选择,有几个核心原则:

  • 数据分布要均匀:分片键的值应该能均匀分布到各个分片上。比如按用户ID取模,如果用户ID是自增的,那数据分布就很均匀。但如果按地区分片,北京的用户特别多,其他地区的用户很少,那就会出现数据倾斜。
  • 查询要友好:分片键最好是业务查询中最常用的条件。比如订单查询,90%的场景都是按用户ID查。那把用户ID作为分片键,就能把查询路由到单个分片上,效率极高。
  • 尽量避免跨分片查询:如果分片键选得不好,很多查询就要扫描所有分片,性能会急剧下降。我曾经遇到过一个系统,分片键选了订单创建时间,但业务查询主要按订单号查。结果每次查询都要扫32个分片,慢得让人崩溃。

常见的分片键选择:

业务场景 推荐分片键 原因
用户系统 用户ID 查询频繁,分布均匀
订单系统 用户ID 或 订单ID 按用户查订单是核心场景
消息系统 消息ID 或 时间戳 按时间范围查询多
日志系统 时间戳 按时间归档和查询

还有一个容易被忽略的点:分片键一旦选定,后续很难修改。因为修改分片键意味着要重新分布所有数据,这个代价非常大。所以,选分片键之前,一定要充分了解业务未来的发展方向。

我的建议:如果实在拿不准选哪个字段做分片键,可以考虑用「复合分片键」。比如先用用户ID取模分到库,再用订单ID取模分到表。这样既能保证数据分布均匀,又能兼顾多种查询场景。当然,实现复杂度会高一些。

好了,这一章的内容就到这儿。分库分表的基础概念其实不复杂,关键是要理解每种方式的适用场景和优缺点。下一章,咱们会深入聊聊分片算法的具体实现,包括取模、哈希、范围分片等。到时候我会结合代码示例,让大家看得更明白。