目录
1、分库分表分区
1.1、联系
1.2、对比
2、分区(Partitioning)
2.1、介绍
2.2、核心原理
2.3、常见分区类型
2.4、分区管理命令
3、分表(Table Sharding)
3.1、介绍
3.2、使用原因
3.3、分片策略设计
3.4、MyBatis + 分表
4、分库(Database Sharding)
4.1、分库原因
4.2、分库分表组合模式
4.3、架构
5、最佳实践
前言
单机 MySQL 的瓶颈如下:
关于分库、分表和读写分离的架构图如下所示;
1、分库分表分区
1.1、联系
| 技术 | 物理位置 | 逻辑视图 | 是否跨实例 |
|---|---|---|---|
| 分区(Partitioning) | 同一表、同一库、同一 MySQL 实例 | 仍是一个表 | 否 |
| 分表(Table Sharding) | 多个表(如user_0,user_1),同一库 | 应用需知道多个表 | 否 |
| 分库(Database Sharding) | 多个数据库(如db_0,db_1),可能跨 MySQL 实例 | 应用需路由到不同库 | 是 |
核心思想:
- 分区:数据库内部优化
- 分库分表:应用层或中间件实现的分布式架构
1.2、对比
| 能力 | 分区 | 分表 | 分库 |
|---|---|---|---|
| 是否跨实例 | ❌ | ❌ | ✅ |
| 应用是否感知 | ❌(透明) | ✅(需拼表名) | ✅(需路由) |
| 突破单机瓶颈 | ❌ | ❌(仍在单库) | ✅ |
| 支持跨分片查询 | ✅(自动裁剪) | ❌ | ❌(需中间件模拟) |
| 扩容难度 | 低 | 高 | 极高 |
| 典型工具 | MySQL 原生 | 应用层 | ShardingSphere, Vitess |
2、分区(Partitioning)
2.1、介绍
MySQL原生支持将一个大表的数据物理拆分到多个“分区”中,但逻辑上仍是一个表,数据库内置能力。
开发人员在数据操作时仍然是对这个整体大表进行操作,之后由数据库底层内部去寻找对应的分区进行操作,这样在数据操作时可以只对特定分区操作以提高效率,存储时也可以将不同分区的物理文件分开存放。
2.2、核心原理
- 物理存储分离:每个分区是一个独立
.ibd文件 - 逻辑视图统一:应用仍操作一个表名
- 查询自动裁剪:优化器只扫描相关分区
2.3、常见分区类型
1.RANGE 分区(按范围)
如下所示:
-- 按年份分区 CREATE TABLE sales ( id BIGINT, sale_date DATE, amount DECIMAL(10,2) ) PARTITION BY RANGE (YEAR(sale_date)) ( PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p_future VALUES LESS THAN MAXVALUE ); -- 查询自动裁剪 EXPLAIN SELECT * FROM sales WHERE sale_date = '2023-06-01'; -- 只扫描 p2023 分区(2) LIST 分区(按枚举值)
如下所示:
-- 按地区分区 CREATE TABLE users ( id BIGINT, region VARCHAR(10) ) PARTITION BY LIST COLUMNS(region) ( PARTITION p_north VALUES IN ('BJ', 'TJ'), PARTITION p_south VALUES IN ('GZ', 'SZ'), PARTITION p_west VALUES IN ('CD', 'XA') );(3) HASH 分区(均匀分布)
如下所示:
-- 按 user_id 均匀分布到 4 个分区 CREATE TABLE orders ( id BIGINT, user_id BIGINT ) PARTITION BY HASH(user_id) PARTITIONS 4;(4) KEY 分区(类似 HASH,支持多列)
如下所示:
PARTITION BY KEY(user_id, order_type) PARTITIONS 8;2.4、分区管理命令
-- 添加新分区 ALTER TABLE sales ADD PARTITION ( PARTITION p2025 VALUES LESS THAN (2026) ); -- 删除旧分区(秒级!) ALTER TABLE sales DROP PARTITION p2022; -- 重建分区(整理碎片) ALTER TABLE sales REBUILD PARTITION p2023;✅ 优点
对应用透明:SQL 不用改,
SELECT * FROM logs WHERE create_time > '2024-01-01'自动只查p2024提升查询性能:分区裁剪(Partition Pruning)
方便数据管理:
ALTER TABLE logs DROP PARTITION p2023;快速删除旧数据
❌ 缺点
仍在单机:无法突破单 MySQL 实例的 CPU/内存/磁盘瓶颈
分区数有限:通常建议 < 100 个分区
不支持所有引擎:仅 InnoDB、MyISAM 支持
📌 适用场景
单表过大(> 1000万行),但总数据量未超单机容量
按时间冷热分离(如日志、订单)
需要快速删除历史数据
3、分表(Table Sharding)
3.1、介绍
将一张大表拆成多张结构相同的表,应用层拆分。
如下所示:
3.2、使用原因
为什么需要分表?
InnoDB 单表建议 < 5000万行(B+树深度增加)
表锁/元数据锁竞争(DDL 操作阻塞)
如:
user_0, user_1, user_2, user_3
如何路由?
通常用分片键(Shard Key) + 取模/哈希决定数据存哪张表。
如下所示:
示例:按 user_id 分 4 张表
// Java 伪代码 public String getTableName(Long userId) { int tableIndex = (int) (userId % 4); return "user_" + tableIndex; } // 查询用户 String sql = "SELECT * FROM " + getTableName(userId) + " WHERE id = ?";3.3、分片策略设计
1.分片键选择
| 字段 | 优点 | 缺点 |
|---|---|---|
| user_id | 用户数据聚集 | 热点用户问题 |
| order_id | 均匀分布 | 无法按用户查订单 |
| tenant_id | 多租户隔离 | 租户数据不均 |
最佳实践:选高频查询字段作为分片键
2.分片算法
// 取模(简单但扩容难) int tableIndex = userId % tableCount; // 一致性哈希(扩容友好) HashFunction hash = Hashing.murmur3_32(); int bucket = hash.hashLong(userId).asInt() & Integer.MAX_VALUE; int tableIndex = bucket % tableCount;3.4、MyBatis + 分表
步骤 1:定义分表路由
@Component public class TableShardingRouter { private static final int TABLE_COUNT = 4; public String getTableName(String baseName, Long shardKey) { int index = (int) (shardKey % TABLE_COUNT); return baseName + "_" + index; } }步骤 2:MyBatis 动态表名
<!-- UserMapper.xml --> <select id="selectById" resultType="User"> SELECT * FROM ${tableName} WHERE id = #{id} </select>步骤 3:Service 层调用
@Service public class UserService { @Autowired private TableShardingRouter router; @Autowired private UserMapper userMapper; public User getUser(Long userId) { String tableName = router.getTableName("user", userId); return userMapper.selectById(tableName, userId); } }优点
突破单表性能瓶颈(InnoDB 单表建议 < 5000万行)
实现简单,无需中间件
缺点
应用强耦合:每个 SQL 都要拼表名;
无法跨表 JOIN / 聚合:select count(*) from user_* 需查 4 次再 sum;
扩容困难:从 4 表扩到 8 表,需迁移一半数据;
适用场景
单库能扛住,但单表太大
查询基本都带分片键(如 where user_id = ?)
4、分库(Database Sharding)
4.1、分库原因
为什么需要分库?
单机资源耗尽(CPU/内存/IO)
连接数瓶颈(
max_connections)多租户数据隔离需求
4.2、分库分表组合模式
模式 1:库内分表(推荐)
4 个库 × 每库 16 张表 = 64 分片
优点:减少数据库连接数
模式 2:仅分库
4 个库 × 每库 1 张表
优点:简化表结构
📊计算公式:
总分片数 = 库数量 × 表数量;
分片ID = hash(shard_key) % 总分片数;
库名 = 分片ID / 表数量;表名 = 分片ID % 表数量
4.3、架构
如下所示:
是什么?
将数据分散到多个数据库实例(可能在不同机器),真正的分布式。
如:
db_0(IP: 10.0.0.1)
db_1(IP: 10.0.0.2)
通常分库 + 分表一起用,如:
db_0
.user_0, db_0.user_1db_1
.user_0, db_1.user_1
架构图
Application
│
├── Router (ShardingSphere / MyCat / 自研)
│ │
│ ├── db_0 (10.0.0.1)
│ │ ├── user_0
│ │ └── user_1
│ │
│ └── db_1 (10.0.0.2)
│ ├── user_0
│ └── user_1
优点
水平扩展:突破单机资源限制(CPU/内存/连接数)
高可用:一个库挂了,其他库仍可用
隔离性:租户/业务线数据物理隔离
缺点
复杂度飙升:
跨库事务(需 Seata/TCC)
跨库 JOIN(需应用层聚合)
全局 ID(需 Snowflake/Leaf)
运维成本高:备份、监控、扩容都变复杂
适用场景
海量数据(> 1TB)
超高并发(> 1万 QPS)
多租户 SaaS 系统
5、最佳实践
1.不要过早分库分表→ 先优化 SQL、加索引、读写分离、缓存
2.分片键至关重要→ 选高频查询字段(如 user_id),避免热点
3.避免跨分片操作→ 设计时让关联数据落在同分片(如订单 & 订单项都按 user_id 分)
4.全局 ID 必须趋势递增→ 避免 UUID 导致 InnoDB 性能下降;
总结
结论:
分区是“治标”,分库分表是“治本”。90% 的系统,用好分区 + 读写分离 + 缓存就足够了;
只有真正遇到单机瓶颈,才考虑分库分表。
参考文章:
1、【MySQL】之分区、分库、分表