1. 为什么需要分库分表?
当业务数据量达到千万级甚至亿级时,单台MySQL服务器的性能瓶颈就会逐渐显现。我经历过一个电商项目,订单表数据量突破3000万后,简单的查询都要2-3秒才能返回结果。更可怕的是"凌晨报表跑批"场景,一个统计SQL直接让数据库CPU飙到100%,连带影响了正常交易。
分库分表的核心价值在于:
- 突破单机存储容量限制(比如单表建议不超过2000万行)
- 分散查询压力(将请求分摊到多个物理节点)
- 避免热点数据竞争(比如用户表按user_id分散)
重要提示:不是所有系统都需要分库分表。当单表数据不足500万时,优先考虑优化索引和SQL。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ShardingSphere方案选型
2.1 主流技术对比
| 方案 | 侵入性 | 学习成本 | 功能完整性 | 运维复杂度 |
|---|---|---|---|---|
| 应用层分片 | 高 | 低 | 不完整 | 低 |
| MyCat | 中 | 中 | 完整 | 高 |
| ShardingSphere | 低 | 中 | 完整 | 中 |
选择ShardingSphere的核心原因:
- 对业务代码零侵入(只需改配置)
- 支持所有主流关系型数据库
- 提供分布式事务能力(虽然性能有损耗)
2.2 版本选择建议
当前稳定版是5.3.2,但需要注意:
- JDK要求1.8+
- Spring Boot项目建议用shardingsphere-jdbc-core-spring-boot-starter
- 需要配套的数据库驱动(如mysql-connector-java 8.0+)
3. 实战分库分表配置
3.1 数据节点规划
以订单表为例,采用"16库×16表"的分片策略:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1,...,ds15
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://db-host-0:3306/order_db_0
username: root
password: 123456
# 其他ds1-ds15配置类似...
3.2 分片算法设计
采用复合分片键(用户ID+时间):
java复制public class OrderShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
long userId = shardingValue.getValue();
// 库分片:userId后4位 mod 16
int dbIndex = (int)(userId & 0xF);
// 表分片:(userId右移4位) mod 16
int tableIndex = (int)((userId >>> 4) & 0xF);
return "ds" + dbIndex + ".order_" + tableIndex;
}
}
3.3 分布式ID生成
避免使用自增ID,推荐方案:
sql复制-- 创建ID生成表
CREATE TABLE sequence (
name VARCHAR(64) PRIMARY KEY,
current_value BIGINT NOT NULL,
step INT NOT NULL DEFAULT 100
);
-- 获取批量ID的存储过程
DELIMITER //
CREATE FUNCTION nextval(seq_name VARCHAR(64)) RETURNS BIGINT
BEGIN
UPDATE sequence SET current_value = current_value + step
WHERE name = seq_name;
RETURN (SELECT current_value - step FROM sequence WHERE name = seq_name);
END//
DELIMITER ;
4. 性能优化关键点
4.1 连接池配置
建议使用HikariCP并调整参数:
yaml复制ds0:
hikari:
maximum-pool-size: 20 # 根据实际压力调整
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
4.2 避免跨库JOIN
典型反例:
sql复制SELECT o.* FROM order o JOIN user u ON o.user_id = u.id
WHERE u.phone = '13800138000'
改造方案:
- 先查用户ID:
SELECT id FROM user WHERE phone = '13800138000' - 再查订单:
SELECT * FROM order WHERE user_id = ?
4.3 索引设计原则
分库分表环境下:
- 必须包含分片键(否则会全库扫描)
- 避免过长索引(影响写入性能)
- 联合索引字段不超过3个
5. 踩坑实录
5.1 分布式事务超时
现象:部分订单支付状态不一致
原因:Seata默认全局事务超时时间为60秒
解决:调整配置并添加重试机制
properties复制# 单位毫秒
seata.tx-service-group.default.grouplist=127.0.0.1:8091
seata.client.tm.degrade-check=false
seata.client.tm.commit-retry-count=3
seata.client.tm.rollback-retry-count=3
5.2 数据倾斜问题
某电商平台发现80%订单集中在3个分片
优化方案:
- 改用一致性哈希算法
- 增加虚拟节点数量(从160调整为1600)
- 历史数据迁移后重新分片
5.3 影子表方案
为应对ALTER TABLE可能导致的锁表:
sql复制-- 创建影子表(结构与原表相同)
CREATE TABLE order_new LIKE order;
-- 数据迁移(业务低峰期执行)
INSERT INTO order_new SELECT * FROM order;
-- 原子切换(RENAME是原子操作)
RENAME TABLE order TO order_old, order_new TO order;
分库分表后,监控变得尤为重要。我们团队自研了一套监控看板,关键指标包括:
- 分片查询命中率(理想值>95%)
- 跨库查询比例(应<5%)
- 单分片最大数据量(警戒线1500万行)
- 分布式事务成功率(要求>99.9%)
