引言:
MySQL的分库分表在工作中是非常常用的,尤其是在高并发、高可用的系统中,下面我们将 分别详细讲解 MySQL 的“分库”和“分表”,包括它们的定义、分类、实现方式、应用场景、优缺点以及实际设计中的注意事项。
一、MySQL 分库
1. 什么是分库?
分库是指将原本存储在一个数据库实例中的多个表或数据,按照一定的规则拆分到 多个独立的数据库(schema)中,这些数据库可以部署在同一个 MySQL 实例上,也可以分布在不同的 MySQL 服务器上。
2. 分库的类型
(1)垂直分库
-
定义:按业务模块划分,把不同功能的表放到不同的数据库中。
-
举例:
- 用户服务 →
user_db(包含 user, address 表) - 订单服务 →
order_db(包含 order, order_item 表) - 商品服务 →
product_db(包含 product, category 表)
- 用户服务 →
-
✅ 优点:
- 业务解耦清晰,便于微服务架构
- 减少单库连接数压力
- 提高安全性和权限控制粒度
-
❌ 缺点:
- 跨库 JOIN 困难(如查询“用户+订单”信息)
- 分布式事务复杂(需要使用 TCC、Saga 等柔性事务)
⚠️ 垂直分库是 逻辑上的业务拆分,常用于微服务架构初期的数据隔离。
(2)水平分库
-
定义:将同一张表的数据根据某个字段(如 user_id)分散到多个数据库中。
-
举例:
- 将
user表按user_id % 4拆分为 4 个库:user_db_0:user_id % 4 = 0user_db_1:user_id % 4 = 1- ...
- 将
-
数据分布均匀,每个库结构相同。
-
✅ 优点:
- 显著降低单库数据量
- 支持高并发写入和读取
- 可通过增加数据库节点实现横向扩展(Scale Out)
-
❌ 缺点:
- 扩容时需重新分配数据(除非使用一致性哈希)
- 跨库聚合查询性能差(如 COUNT、AVG)
- 主键全局唯一问题(不能依赖自增 ID)
3.分库的实现方式:
| 方式 | 说明 |
|---|---|
| 应用层路由 | 在代码中判断该访问哪个库(如根据 user_id 计算) |
| 中间件代理 | 使用 MyCat、ShardingSphere-Proxy 等中间件自动路由 SQL |
| JDBC 层拦截 | 使用 ShardingSphere-JDBC,在 JDBC 层解析并路由 SQL |
4. 分库的应用场景
- 单库连接数接近上限(如 max_connections=1500 已满)
- 单库 QPS/TPS 过高,CPU 或 IO 达到瓶颈
- 数据总量过大,影响备份恢复效率
- 多租户系统中按 tenant_id 隔离数据
5.分库的设计要点
- 选择合适的分片键(Sharding Key):如 user_id、order_id,要求分布均匀、高频查询。
- 避免跨库事务:尽量用最终一致性替代强一致性。
- 统一主键生成策略:如雪花算法(Snowflake)、UUID、Redis 自增等。
- 监控与运维工具:统一管理多个数据库实例。
二、MySQL 分表(Table Sharding)
1. 什么是分表?
分表 是指将一个大表的数据拆分成多个结构相同的表(或不同结构),以减少单表的数据量,提高查询和维护效率。
2. 分表的类型
(1)垂直分表(Vertical Table Splitting)
-
定义:将一张宽表按列拆分为多个窄表,通常基于“冷热分离”原则。如按照日期垂直分表
-
✅ 优点:
- 减少 I/O 开销(只查 base 表即可)
- 提升缓存命中率
- 优化索引大小
-
❌ 缺点:
- 查询完整信息需要 JOIN
- 维护成本上升(外键、事务等)
⚠️ 垂直分表适合字段多、部分字段访问频率差异大的场景。
(2)水平分表(Horizontal Table Splitting)
- 定义:将一张表的行数据按某种规则拆分到多个结构相同的表中。
- 常见拆分方式:
全屏复制
| 拆分方式 | 示例 |
|---|---|
| 按时间拆分 | log_2023, log_2024, order_202301, order_202302 |
| 按ID取模 | user_0, user_1, ..., user_7(user_id % 8) |
| 按范围拆分 | user_0_100w, user_100w_200w |
| 一致性哈希 | 用于动态扩容,减少数据迁移 |
-
✅ 优点:
- 单表数据量显著下降
- 查询性能提升(索引更小、B+树层级更低)
- 易于归档历史数据(如删除旧月表)
-
❌ 缺点:
- 跨表查询困难(如 SELECT COUNT(*) FROM ALL user_xxx)
- 分页查询复杂(LIMIT OFFSET 性能差)
- DDL 操作需批量执行(如加索引要改所有表)
⚠️ 水平分表常用于日志、订单、消息等快速增长的表。
3. 分表的实现方式:
| 方法 | 说明 |
|---|---|
| 手动分表 + 应用路由 | 开发人员在代码中决定操作哪张表 |
| 使用分片框架 | 如 ShardingSphere,配置后自动路由到对应表 |
| 视图合并(有限支持) | 创建 VIEW 联合多个子表(仅适用于查询) |
| 分区表(Partitioning) | MySQL 原生支持 RANGE、LIST、HASH 分区(不是真正分表,但类似效果) |
4. 分表的应用场景
- 单表记录超过 千万级,查询变慢
- 表中有大量历史数据,近期数据访问频繁(适合时间分片)
- 写入压力大,自增锁竞争严重(可通过分表缓解)
- 需要定期归档或删除旧数据(如日志表)
5. 分表的设计要点
- 分片键选择:必须是查询条件常用字段(如 user_id、create_time)
- 分片数量预估:提前规划未来几年的数据增长,避免频繁扩容
- 命名规范:如
table_name_shard0000到shard1023 - 自动化运维脚本:批量建表、加索引、迁移数据
- 结合缓存:热点数据放入 Redis,减少对分表的直接访问
三、分库 vs 分表 对比总结
| 维度 | 分库 | 分表 |
|---|---|---|
| 拆分维度 | 数据库存储层面 | 表结构层面 |
| 主要目的 | 解决连接数、IO、CPU 瓶颈 | 解决单表数据量过大 |
| 部署位置 | 可跨服务器 | 通常在同一数据库内 |
| 扩展性 | 更强(可加机器) | 有限(受限于单机资源) |
| 复杂度 | 高(涉及分布式事务) | 中等 |
| 典型场景 | 高并发、大数据量系统 | 大表性能优化 |
四、根据经验什么时候应该分库、分表
1.一般多少数据量应该分表?
| 单表数据量 | 建议动作 |
|---|---|
| 超过 500 万行 | 开始关注,评估是否需要分表 |
| 超过 1000 万行(1千万) | 强烈建议考虑分表 |
| 超过 5000 万行 | 必须分表,否则性能严重下降 |
| 超过 1 亿行 | 已经属于“大表”,必须已分表或使用分布式数据库 |
如果不分会怎么样?
| 问题 | 说明 |
|---|---|
| B+树索引层级变深 | 数据越多,B+树层数增加(3层 → 4层),导致磁盘 I/O 增加 |
| 缓冲池命中率降低 | 大表索引无法完全加载到 innodb_buffer_pool,频繁读磁盘 |
| 锁竞争加剧 | DML 操作(UPDATE/DELETE)容易引发行锁、间隙锁争用 |
| DDL 变慢甚至阻塞 | 如 ALTER TABLE ADD INDEX 可能耗时数小时 |
| 备份恢复困难 | 单表过大导致 mysqldump 或 xtrabackup 时间过长 |
| 主从延迟风险高 | 大事务同步慢,造成主从延迟 |
✅ 正确做法:
- 监控单表数据增长趋势
- 当接近 500万~1000万行 时,启动分表方案设计
- 在 1000万行以内完成分表迁移
2.什么情况下应该分库?
| 判断维度 | 具体表现 | 是否应分库 |
|---|---|---|
| 1. 单库连接数接近上限 | show status like 'Threads_connected'; 接近 max_connections(如 1500) | ✅ 必须分库 |
| 2. CPU 或 IO 达到瓶颈 | 主库 CPU 长期 >70%,磁盘 IOPS 饱和 | ✅ 考虑分库 |
| 3. 写入 QPS 过高 | 单库写入 TP99 延迟上升,事务排队 | ✅ 分库缓解压力 |
| 4. 数据总量过大 | 单库总数据量超过 100GB ~ 1TB | ✅ 建议分库 |
| 5. 业务模块耦合严重 | 用户、订单、商品都在一个库,维护混乱 | ✅ 垂直分库解耦 |
| 6. 多租户隔离需求 | SaaS 系统中不同客户数据需物理隔离 | ✅ 按 tenant_id 分库 |
| 7. 微服务架构演进 | 各服务应拥有独立数据库 | ✅ 垂直分库支持微服务 |
分库的数据量参考标准(经验)
| 单库总数据量 | 建议 |
|---|---|
| > 100GB | 关注,准备垂直拆分 |
| > 500GB | 强烈建议水平分库或垂直分库 |
| > 1TB | 必须分库,否则运维困难 |
五、页分裂-分库性能陷阱
1.什么是页分裂?
之前文章有讲过mysql的物理存储最小都是已页存储,如果不使用id自增的情况下会导致
随机写入,当一页已满,再插入新数据时,如果无法容纳,就会触发 页分裂(Page Split)
mysql--核心日志文件详解
https://blog.youkuaiyun.com/zhang09090606/article/details/152515373
| 主键类型 | 插入位置 | 是否有序 | 页分裂频率 |
|---|---|---|---|
| 自增主键(如 AUTO_INCREMENT) | 总是在最后一个页追加 | ✅ 有序 | ❌ 极少 |
| 随机主键(如 UUID、Snowflake 无序) | 随机插入到中间某个页 | ❌ 无序 | ✅ 频繁 |
2.页分裂带来的负面影响
| 问题 | 说明 |
|---|---|
| 性能下降 | 分裂涉及内存拷贝、磁盘写、B+树结构调整 |
| 空间浪费 | 分裂后每页利用率降低(如从 90% → 50%) |
| 碎片化严重 | 物理存储不连续,影响顺序读性能 |
| Buffer Pool 效率低 | 更多页被加载进内存,命中率下降 |
| I/O 增加 | 频繁刷脏页(dirty page flush) |
3.分库场景中是无法使用自增主键的,那么如何避免呢?
方案 1:使用“趋势递增”的分布式 ID(推荐)
推荐方案:改良版 Snowflake ID
- 保证同一节点上的 ID 是严格递增的
- 不同节点之间尽量时间上错开(通过数据中心 ID + 机器 ID 控制)
✅ 优势:
- 同一毫秒内序列号递增
- 跨毫秒时时间戳递增 → 整体趋势向上
- 插入集中在末尾页附近 → 减少页分裂
📌 实测:趋势递增 ID 的性能接近自增 ID 的 80%~90%
方案 2:使用 BIGINT + 分片字段组合主键(联合主键)
- 主键
(shard_id, seq_id)是局部有序的 - 每个分片独立自增 → 插入始终在末尾
- 用
order_no做全局查询索引
📌 缺点:需要额外维护 shard_id
方案 3:避免使用主键做聚簇索引(冷门但有效)
InnoDB 的聚簇索引必须基于主键,但我们可以“欺骗”它:
id是逻辑主键(UUID)auto_inc_id是隐藏的自增列,带UNIQUE约束- InnoDB 会选择
auto_inc_id作为聚簇索引(因为更小、有序)
⚠️ 注意:这不是官方支持的做法,需测试验证
方案 4:调整页填充因子(Fill Factor)
MySQL 支持设置页的填充比例,预留空间减少分裂:
- 每页预留 20% 空间 → 减少分裂频率
- 缺点:浪费存储空间
4.开发建议
| 场景 | 建议 |
|---|---|
| 新项目分库分表 | 使用 趋势递增 Snowflake ID(如美团 Leaf、百度 UidGenerator) |
| 已有系统迁移 | 可考虑引入 auto_inc_id 辅助列缓解问题 |
| 高频插入场景 | 避免 UUID 直接作主键;优先选择数字型、趋势递增 ID |
| 查询为主场景 | 可接受一定页分裂,重点保障查询索引设计 |
1466

被折叠的 条评论
为什么被折叠?



