分库分表基础:数据分片概念、垂直分库与水平分库、垂直分表与水平分表、分片键的选择策略
好,咱们正式开始聊分库分表。说实话,很多同学一听到这四个字就觉得头大,觉得是特别高深的东西。其实没那么玄乎。你想想看,数据库撑不住了,无非就是两个问题:一个是数据太多了,一个是请求太猛了。分库分表,就是针对这两个问题给出的解决方案。
我个人习惯把分库分表拆成两个维度来看:一个是「分库」,一个是「分表」。然后再各自拆成「垂直」和「水平」两个方向。这样一组合,就是四个核心概念。咱们一个一个来过。
数据分片:到底在分什么?
先说说数据分片这个概念。说白了,就是把一个大数据库里的数据,拆成多个小数据库来存。每个小数据库,就是一个「分片」。
我记得刚入行那会儿,带我的老大哥跟我说过一句话:「分片不是目的,让系统能扛住才是目的。」这句话我一直记到现在。分片之后,每个分片只承担一部分数据和一部分请求,压力自然就小了。
数据分片的核心思路,就是「分而治之」。把一个大问题拆成多个小问题,每个小问题单独解决。这个思路在计算机领域随处可见,分库分表只是其中一个应用场景。
垂直分库:按业务拆
垂直分库,说白了就是「按业务拆库」。比如你原来有一个大库,里面既有用户表、订单表,又有商品表、库存表。随着业务发展,这个库扛不住了。怎么办?
我建议的做法是:把用户相关的表放到一个库,订单相关的表放到另一个库,商品相关的表放到第三个库。这就是垂直分库。
垂直分库的核心原则:每个库只负责一个业务域,库与库之间通过接口或服务调用,而不是直接跨库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取模分到表。这样既能保证数据分布均匀,又能兼顾多种查询场景。当然,实现复杂度会高一些。
好了,这一章的内容就到这儿。分库分表的基础概念其实不复杂,关键是要理解每种方式的适用场景和优缺点。下一章,咱们会深入聊聊分片算法的具体实现,包括取模、哈希、范围分片等。到时候我会结合代码示例,让大家看得更明白。