大家好,我是工藤学编程 | 一个正在努力学习的小博主,期待你的关注 |
---|---|
实战代码系列最新文章 | C++实现图书管理系统(Qt C++ GUI界面版) |
SpringBoot实战系列 | 【SpringBoot实战系列】Sharding-Jdbc实现分库分表到分布式ID生成器Snowflake自定义wrokId实战 |
环境搭建大集合 | 环境搭建大集合(持续更新) |
分库分表 | 分库分表之数据库分片分类 |
前情摘要:
1、数据库性能优化
2、分库分表之优缺点分析
3、分库分表之数据库分片分类
RANGE拆分是按数据主键的取值范围将数据划分到不同分表/分库中,本质是通过区间分段实现数据分片。其核心规则是定义连续的区间段,每个区间对应一个分片,典型应用场景包括:
以订单表为例,基于自增IDorder_id
进行RANGE拆分:
分片规则:
table_1: order_id ∈ [1, 1000000)
table_2: order_id ∈ [1000000, 2000000)
table_3: order_id ∈ [2000000, 3000000)
...
table_n: order_id ∈ [(n-1)*1000000, n*1000000)
建表示例:
CREATE TABLE `order_01` (
`order_id` bigint(20) NOT NULL COMMENT '订单ID(1-100万)',
`user_id` bigint(20) NOT NULL,
`create_time` datetime NOT NULL,
-- 其他字段
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB CHARSET=utf8;
CREATE TABLE `order_02` (
`order_id` bigint(20) NOT NULL COMMENT '订单ID(100万-200万)',
`user_id` bigint(20) NOT NULL,
`create_time` datetime NOT NULL,
-- 其他字段
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB CHARSET=utf8;
按时间粒度(年/月/日)拆分数据库,适用于日志、交易等有时间序列特征的数据:
分库规则:
log_2024: 存储2024年全年日志
log_2023: 存储2023年全年日志
log_2022: 存储2022年全年日志
核心SQL路由逻辑:
// 根据日志时间确定分库
String year = dateFormat.format(logTime);
String dbName = "log_" + year;
按物理位置、云服务器节点或地域属性拆分,实现数据本地化存储:
分库规则(按地域):
db_beijing: 华北地区数据(order_id ∈ [1, 1000万))
db_shanghai: 华东地区数据(order_id ∈ [1000万, 2000万))
db_guangzhou: 华南地区数据(order_id ∈ [2000万, 3000万))
分表规则(按服务器节点):
table_node1: 服务器A数据(order_id % 3 = 0)
table_node2: 服务器B数据(order_id % 3 = 1)
table_node3: 服务器C数据(order_id % 3 = 2)
核心路由逻辑(地域+ID复合):
// 1. 按地域划分大区间
String region = getRegionByUserIp(userIp);
int regionBaseId = regionMap.get(region); // 华北=0,华东=1000万,华南=2000万
// 2. 按ID范围分库
long orderId = ...;
int dbIndex = (int)((orderId - regionBaseId) / 10000000);
String dbName = "db_" + region + "_" + dbIndex;
扩容无数据迁移成本
table_n+1
对应[n*100万, (n+1)*100万)
),无需移动历史数据。table_10
写满后,直接创建table_11
并配置区间,新数据自动写入新表。规则简单易维护
历史数据归档便捷
问题类型 | 具体表现 | 影响程度 |
---|---|---|
数据热点集中 | 最新区间(如table_n )或热点地域(如北上广分库)承担90%以上读写请求,导致单表/单库IO瓶颈 |
★★★★★ |
资源利用率低 | 历史区间(如table_1 )或非热点地域分库数据极少被访问,硬件资源浪费 |
★★★☆☆ |
跨分片查询复杂 | 范围查询(如order_id > 500万 )或跨地域统计需扫描多个分片,性能随分片数增加而下降 |
★★★☆☆ |
区间边界设计困难 | 区间大小难以预估:过小导致分片数激增,过大导致单表数据量再次超限;空间维度中地域流量不均衡可能导致分库负载失衡 | ★★★★☆ |
Hash取模是通过对分片字段(如用户ID、订单ID)进行哈希计算后取模,将数据均匀分配到不同库表中。其核心逻辑是利用哈希函数的离散性,确保数据在分片节点上的均衡分布,典型应用场景包括:
以用户ID分库分表为例,目标是拆分为2个库,每个库包含4张表(共8张表):
分片规则:
库路由:db_idx = userId % 2
表路由:table_idx = (userId / 2) % 4
注:为什么table_idx不直接(userId ) % 4 呢?
这种方式会导致每个数据库会存储所有表的一部分数据,并且不同数据库里相同表索引的表所存数据是不一样的。这将可能导致数据不均匀的情况。
示例验证
假设用户 ID 为偶数(如 2、4、6):
策略table_idx =(userId ) % 4:这些 ID 会被分到不同的表中,例如 2 → 表 2,4 → 表 0,6 → 表 2。
策略table_idx = (userId / 2) % 4:这些 ID 会被分到同一张表,例如 2 → 表 1,4 → 表 2,6 → 表 3。
可以看到,我们提出的策略将会使得数据更加均匀
直接取模的缺陷
userId % 4 的结果仅由 ID 末两位决定(二进制角度),当 ID 按奇偶分段(如分 2 库)时,偶数 ID 的末两位可能集中在 00、10 等模式,导致取模结果偏向特定值(如策略一中偶数 ID%4 结果多为 0 或 2)。
先除再取模的原理
将 ID 除以 2(userId / 2)相当于将 ID 范围「压缩」一半,例如:
原 ID 范围:2,4,6,8,10,12 → 压缩后:1,2,3,4,5,6
压缩后的数值对 4 取模,等价于将原 ID 范围按每 8 个 ID(2 库 ×4 表)为一组重新分段,每组内的 ID 会均匀分布到 4 张表:
组 1(ID=2-9):压缩后 1-4 → %4 得 1,2,3,0
组 2(ID=10-17):压缩后 5-8 → %4 得 1,2,3,0
数据路由示例:
userId | db_idx(库) | table_idx(表) | 目标库表 |
---|---|---|---|
1 | 1%2=1 | (1/2)=0 → 0%4=0 | db_1.table_0 |
2 | 2%2=0 | (2/2)=1 → 1%4=1 | db_0.table_1 |
8 | 8%2=0 | (8/2)=4 → 4%4=0 | db_0.table_0 |
9 | 9%2=1 | (9/2)=4 → 4%4=0 | db_1.table_0 |
建表示例:
-- 库1(db_1)中的表
CREATE TABLE `user_0` ( ... ); -- table_idx=0
CREATE TABLE `user_1` ( ... ); -- table_idx=1
CREATE TABLE `user_2` ( ... ); -- table_idx=2
CREATE TABLE `user_3` ( ... ); -- table_idx=3
-- 库2(db_2)中的表
CREATE TABLE `user_0` ( ... ); -- table_idx=0
CREATE TABLE `user_1` ( ... ); -- table_idx=1
CREATE TABLE `user_2` ( ... ); -- table_idx=2
CREATE TABLE `user_3` ( ... ); -- table_idx=3
当分片字段为字符串(如手机号、邮箱)时,需先通过哈希函数转为整数再取模:
// 手机号分库分表示例
String phone = "13800138000";
int hashCode = phone.hashCode(); // 转为哈希值
int dbIdx = Math.abs(hashCode) % 2; // 库路由
int tableIdx = Math.abs(hashCode) / 2 % 4; // 表路由
哈希函数选择建议:
hashCode()
或Pythonhash()
;MurmurHash
、FNVHash
等高性能哈希算法,降低哈希冲突概率。数据均匀分布,避免热点集中
单用户数据聚合,查询效率高
规则简单,适合高并发场景
问题类型 | 具体表现 | 影响程度 |
---|---|---|
扩容需全量数据迁移 | 从N库扩至2N库时,需重新计算所有数据的%2N ,并迁移至新库 |
★★★★★ |
迁移期间服务波动 | 全量迁移需停机或读写分离,可能导致分钟级至小时级服务不可用 | ★★★★☆ |
哈希冲突风险 | 不同字段哈希后取模结果相同(如手机号13800138000 与13900139000 哈希值相同) |
★★☆☆☆ |
Hash取模作为最常用的分库分表策略,通过简单规则实现数据均匀分布,尤其适合用户维度强相关的高并发场景。尽管存在扩容成本高的缺点,但通过复合拆分策略与中间件优化,可在数据均衡性与可扩展性之间找到平衡点,是互联网业务落地分库分表的首选方案之一。