数据分片与数据库分库分表
高并发写场景可以通过数据分片把数据或请求拆成多份并行处理,数据库中的典型实现是分库分表;分表解决单表数据量问题,分库解决单库并发和资源瓶颈,垂直拆分侧重业务和字段拆分,水平拆分侧重数据行拆分,而范围分区、哈希分区、一致性哈希和从库升级扩容共同构成了分库分表落地时最关键的数据路由与扩容能力。
数据分片与数据库分库分表
一、核心主线
本节讲的是高并发写场景的第一种通用方案:数据分片,其中最典型的形式是数据库分库分表。
数据分片的核心思想是:
将待处理的数据或请求拆成多份,并让多个分区并行处理,从而提升系统整体吞吐能力。
类比现实生活:
- 医院开多个挂号/收费窗口;
- 地铁站设置多个闸机口;
- 多个窗口并行服务,减少单点排队压力。
在数据库场景中:
1 | 单库单表 |
二、为什么需要分库分表
随着业务发展,数据库通常会面临两类压力。
1. 数据量大
当单表数据达到千万级甚至上亿行时:
- SQL 扫描行数增加;
- 索引结构变深;
- 磁盘 I/O 次数增加;
- DDL 表结构变更耗时变长;
- 单表读写性能下降。
以 MySQL 为例,书中提到单表超过 2000 万行后,B+ 树层级可能增多,读写磁盘 I/O 成本会上升。
此时通常需要分表。
2. 并发量大
当海量用户同时访问单个数据库时,数据库服务器可能在以下资源上达到瓶颈:
- 最大连接数;
- CPU;
- 内存;
- 网络带宽;
- 磁盘 I/O。
此时通常需要分库,利用多台服务器共同承担压力。
3. 分库与分表的目标区别
| 操作 | 主要目标 |
|---|---|
| 分表 | 降低单表数据量,提高单表 SQL 执行效率 |
| 分库 | 利用多台服务器资源,提高整体并发处理能力 |
三、分库与分表
1. 分库
分库是将一个数据库拆成多个小数据库。
1 | 原数据库 |
原来集中在一个数据库中的数据,被分散到多个数据库中。
2. 分表
分表是将一个数据表拆成多个结构一致的小表。
1 | 原用户表 |
每张表结构一致,但保存不同范围或不同分区的数据。
3. 分库分表
实际架构中,分库和分表通常一起使用,因此统称为数据库分库分表。
1 | 单库单表 |
四、垂直拆分
垂直拆分侧重按照业务或字段拆分,可以分为垂直分库和垂直分表。
1. 垂直分库
垂直分库是按照业务归属拆分数据库,核心是:
专库专用。
例如电商系统最初可能只有一个电商库:
1 | 电商库 |
业务发展后,可以拆成:
1 | 商品库 |
垂直分库的价值
- 不同业务数据解耦;
- 不同业务团队可以独立维护;
- 职责边界更清晰;
- 不同业务库可以部署在不同服务器上;
- 数据库并发能力得到一定提升。
垂直分库的局限
- 不能解决单业务内部数据量过大的问题;
- 如果某个业务库本身流量极高,仍需要继续拆分;
- 跨业务查询和事务会更复杂。
2. 垂直分表
垂直分表是把一个表按照字段拆成多个表。
拆分依据通常包括:
- 字段访问频率;
- 字段值大小;
- 字段是否属于核心数据;
- 字段是否只在低频场景使用。
例如商品表:
1 | 商品表 |
可以拆成:
1 | 商品基本信息表 |
垂直分表的价值
- 隔离核心字段和非核心字段;
- 高频字段表更小;
- 单行数据更轻;
- 数据库可以在内存中加载更多核心数据;
- 提高缓存命中率;
- 减少磁盘 I/O。
垂直分表的局限
垂直分表没有减少总行数。
因此,它适合:
- 字段多;
- 字段大小差异大;
- 高频字段和低频字段差异明显;
- 但单表数据量本身还没有特别大。
如果单表行数已经过大,垂直分表无法根本解决问题。
五、水平拆分
水平拆分侧重按照数据行拆分,包括水平分库和水平分表。
1. 水平分库
水平分库是将同一个数据库中的数据按照某种规则拆到多个数据库中。
每个数据库中的表结构与拆分前一致。
例如用户库拆为 3 个库:
1 | 用户库 |
路由规则可以是:
1 | uid % 3 |
不同 uid 的用户数据被分配到不同数据库。
水平分库的价值
- 利用多台服务器资源;
- 提高数据库整体并发处理能力;
- 单库压力下降;
- 拆分后单库内的数据量也能得到一定控制。
2. 水平分表
水平分表是在同一个数据库内,将一个表的数据按照规则拆到多个表中。
1 | 用户库 |
表结构完全一致,但保存不同用户数据。
水平分表的价值
- 解决单表数据量过大;
- 降低单表索引高度;
- 减少单表查询和写入成本;
- 提高单库内 SQL 执行效率。
水平分表的局限
拆分后的表仍在同一个数据库中,仍会竞争同一台服务器的:
- 连接数;
- CPU;
- 内存;
- 网络带宽;
- 磁盘资源。
因此,如果并发量也很大,仅水平分表不够,还需要结合水平分库。
3. 水平分库分表
水平分库分表是将数据同时拆到多个库和多个表中。
1 | 原用户库.用户表 |
它同时解决:
- 单表数据量过大;
- 单库并发量过大;
- 单机资源瓶颈。
六、水平拆分选型
根据业务压力类型选择拆分方式:
| 业务特点 | 推荐方案 |
|---|---|
| 并发量大,数据量较小 | 水平分库 |
| 并发量小,数据量较大 | 水平分表 |
| 并发量大,数据量也大 | 水平分库分表 |
选型原则:
1 | 数据量问题 → 分表 |
七、水平拆分规则
水平拆分规则本质上是数据路由算法。
它决定一条数据应该进入哪个数据分区:
1 | 数据 |
理想的数据路由算法应尽量保证:
- 各分区数据量均衡;
- 各分区读写请求量均衡;
- 不出现明显数据偏斜;
- 不出现明显数据热点;
- 扩容时迁移成本可控。
1. 数据偏斜与数据热点
| 问题 | 含义 |
|---|---|
| 数据偏斜 | 某些分区存储的数据量明显大于其他分区 |
| 数据热点 | 某些分区承担的读写请求量明显大于其他分区 |
数据路由算法需要尽量避免这两类问题。
八、范围分区法
1. 基本思路
范围分区法根据可排序字段的区间划分数据。
常见字段:
- 数据唯一 ID;
- 创建时间;
- 自增编号。
例如按创建时间拆分:
1 | 2020.07~2020.12 → DB1 |
2. 优点
- 分区规则直观;
- 支持范围查询;
- 按时间、ID 查询某个区间数据很方便;
- 扩容较友好,新增分区时可以追加新的数据范围。
3. 缺点
范围分区是否均衡,高度依赖分区字段特性。
如果字段是自增 ID,等长范围较容易保证数据量均匀。
但如果字段随机性强,例如用户昵称:
- 不同范围的数据量可能差异很大;
- 容易造成数据偏斜。
如果字段带有时间属性:
- 最近时间范围的数据读写频率通常更高;
- 容易造成数据热点。
九、哈希分区法
1. 基本思路
哈希分区通过哈希函数计算分区。
最简单方式是取模:
1 | partition = hash(key) % N |
其中 N 是分区数量。
2. 优点
- 实现简单;
- 不依赖字段是否自增或随机;
- 优秀哈希函数可以使数据分布更均匀;
- 能较好避免数据偏斜和热点。
3. 缺点
简单取模对扩容不友好。
当 N 发生变化:
1 | hash(key) % N |
的结果会大面积变化,导致大量数据需要重新分区和迁移。
4. 哈希值范围分区
更好的做法是将哈希法与范围分区结合:
1 | 先计算 hash(key) |
这样既可以利用哈希函数打散数据,又可以通过范围管理提升扩容灵活性。
十、一致性哈希分区法
1. 哈希环
一致性哈希的核心结构是哈希环。
1 | 0 ~ 2^32 - 1 的数值首尾相连形成环 |
每个数据分区通过哈希计算映射到环上的某个位置。
每条数据也通过哈希计算映射到环上的某个位置。
2. 数据路由规则
数据映射到哈希环后,从该位置开始顺时针查找,第一个遇到的数据分区负责保存该数据。
1 | data hash 位置 |
3. 扩缩容优势
一致性哈希最主要的优点是:增加或删除分区时,只影响相邻局部数据。
如果某个分区被移除:
- 只有该分区原本负责的数据需要迁移;
- 其他分区的数据不受影响。
相比简单取模,数据迁移规模大幅降低。
4. 数据偏斜问题
如果实际分区数量较少,它们在哈希环上的位置可能分布不均。
结果是:
- 某些分区负责很大的哈希范围;
- 某些分区负责很小的哈希范围;
- 数据量分布不均;
- 出现数据偏斜。
5. 虚拟节点
为缓解数据偏斜,一致性哈希引入虚拟节点。
一个真实分区对应多个虚拟节点:
1 | Partition 1 |
虚拟节点越多,哈希环上的节点分布越均匀,数据偏斜影响越小。
十一、扩容方案:从库升级法
1. 为什么需要扩容
当出现以下情况时,需要扩容:
- 某个分库数据量远高于其他分库;
- 某个分库请求量远高于其他分库;
- 现有分库分表架构趋于饱和;
- 单个分区成为瓶颈。
2. 单个分库扩容流程
假设 DBO 的数据范围是:
1 | [r0, r1) |
使用从库升级法扩容:
- 为 DBO 增加 Slave,开始主从复制;
- 等待主从数据同步完成;
- 临时封禁 DBO 主库写请求,避免继续产生增量数据;
- 检查主从数据是否一致;
- 数据一致后断开主从关系;
- 修改原 DBO 数据范围为前一半:
1 | [r0, (r0+r1)/2) |
- 将 DBO 的 Slave 提升为新主库 DB3;
- 设置 DB3 数据范围为后一半:
1 | [(r0+r1)/2, r1) |
- 等待上游业务感知新的分库范围;
- 解封写请求;
- 启动离线任务删除两个库中不属于自身范围的冗余数据。
3. 翻倍扩容法
如果对所有分库都执行从库升级,每个原分库都拆成两个分库,分库数量会翻倍。
因此,这种方式也叫:
翻倍扩容法。
整体流程与单分库扩容类似,只是对每个分库同时执行:
1 | 原分库 |
4. 从库升级法的特点
优点:
- 利用已有主从复制能力;
- 减少全量数据重新搬迁成本;
- 扩容过程相对平滑;
- 业务停写时间较短。
代价:
- 仍需要短暂封禁写请求;
- 需要业务方及时感知路由范围变更;
- 扩容后需要离线清理冗余数据;
- 依赖主从数据一致性检查。
十二、其他数据分片形式
数据分片不只存在于数据库分库分表中,本节还列举了三个例子。
1. Kafka 多 Partition
Kafka 使用 Topic 表示逻辑消息队列。
每个 Topic 可以拆成多个 Partition:
1 | Topic |
每个 Partition 对应独立日志文件,并可分布在不同服务器上。
Topic 的 Partition 越多,消息写入可以越并行,整体写入吞吐量越高。
本质上,Kafka Partition 也是一种数据分片。
2. 秒杀系统分布式锁分片
秒杀系统常用分布式锁防止库存超卖。
但如果所有请求竞争同一把锁:
1 | 所有秒杀请求 → 同一把锁 → 串行执行 |
性能会非常差。
优化思路是将库存拆成多份,每份库存用独立锁保护。
例如 1000 台 iPhone 拆成 20 个库存分段:
1 | seg-0 ~ seg-19 |
请求路由:
1 | 用户ID % 20 = i |
如果一次锁操作耗时 20 ms:
- 单锁每秒最多约 50 个请求;
- 20 把锁并行后,每秒可处理约 1000 个请求。
3. ConcurrentHashMap
ConcurrentHashMap 也是数据分片思想的应用。
它不会对整个 HashMap 加一把全局锁,而是将内部数据拆分到多个槽,并对每个槽独立加锁。
1 | HashMap |
这样可以减少线程竞争,提高并发读写性能。
十三、核心架构思想
- 数据分片的本质是把单点压力拆成多份并行处理。
- 数据库分库分表是数据分片在高并发写场景中的典型应用。
- 分表主要解决单表数据量过大问题。
- 分库主要解决单库并发和单机资源瓶颈问题。
- 垂直拆分按业务或字段拆,强调业务解耦和冷热字段隔离。
- 水平拆分按数据行拆,强调数据量和请求量的分散。
- 选择水平分库、水平分表还是水平分库分表,应取决于并发量和数据量。
- 数据路由算法要尽量避免数据偏斜和数据热点。
- 范围分区适合范围查询和追加扩容,但容易受字段分布影响。
- 哈希分区能更均匀地打散数据,但简单取模扩容成本高。
- 一致性哈希通过哈希环降低扩缩容时的数据迁移范围。
- 虚拟节点可以缓解一致性哈希中的数据偏斜问题。
- 从库升级法利用主从复制实现相对平滑的分库扩容。
- 数据分片思想也适用于 Kafka、秒杀库存锁、ConcurrentHashMap 等非数据库场景。