MySQL分库分表什么时候该动手
单表数据量突破千万行之后,查询延迟肉眼可见地上升——即使索引完整,范围查询和排序操作的响应时间也会从毫秒级退化到秒级。分库分表是解决这个问题的常规武器,但过早分片和错误拆分策略带来的运维代价远大于性能收益。本文从拆分时机判断、垂直拆分到水平扩展,给出一套完整的分库分表实施路径。
分库分表的触发条件:别被数据量吓到
不是数据量大就要分片。以下指标出现2个以上才需要认真考虑:
- 单表行数超过3000万,且持续增长
- 慢查询日志中单表查询占比超过60%
- 单表数据文件超过50GB,备份耗时超过业务窗口
- 写入QPS超过单机MySQL的写上限(约5000-8000 QPS,取决于硬件)
# 评估单表健康状态
SELECT
table_name,
table_rows,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_mb
FROM information_schema.tables
WHERE table_schema = 'order_db'
ORDER BY total_mb DESC;
# 慢查询统计
SELECT
LEFT(query_text, 80) AS query_preview,
exec_count,
avg_timer_ms / 1000 AS avg_ms,
rows_examined / exec_count AS avg_rows_scanned
FROM performance_schema.events_statements_summary_by_digest
WHERE schema_name = 'order_db'
AND avg_timer_ms / 1000 > 100
ORDER BY avg_timer_ms DESC LIMIT 20;
垂直拆分:先做业务边界划分
垂直拆分的本质是把一个大库按业务域拆成多个小库。这一步通常不需要中间件。
# 原始订单库表结构
order_db:
├── users (用户表)
├── orders (订单表)
├── order_items (订单明细)
├── payments (支付记录)
├── products (商品表)
├── inventory (库存表)
└── user_logs (用户行为日志)
# 垂直拆分后
user_db: users, user_logs
order_db: orders, order_items, payments
product_db: products, inventory
垂直拆分的核心原则:高内聚、低耦合。同一个事务中操作的表应该放在同一个库。如果拆分后发现跨库事务大量出现,说明拆分边界选错了。
水平分片:ShardingSphere实战配置
垂直拆分后如果单表仍然过大,进入水平分片阶段。ShardingSphere是目前最成熟的Java分片中间件。
# Spring Boot + ShardingSphere 5.5.0 配置
# application.yml
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://10.0.1.10:3306/order_db_0?useSSL=false
username: root
password: Order@2026
hikari:
maximum-pool-size: 20
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://10.0.1.11:3306/order_db_1?useSSL=false
username: root
password: Order@2026
hikari:
maximum-pool-size: 20
rules:
sharding:
tables:
orders:
actual-data-nodes: ds${0..1}.orders_${0..15}
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: orders-table-inline
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: orders-db-mod
sharding-algorithms:
orders-table-inline:
type: INLINE
props:
algorithm-expression: orders_${user_id % 16}
orders-db-mod:
type: MOD
props:
sharding-count: 2
key-generate-strategies:
orders:
key-generate-algorithm-name: snowflake
key-generate-algorithms:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
配置要点解析:
actual-data-nodes:定义数据分布,2个库×16张表=32个分片- 分库策略:
user_id % 2决定数据落入ds0还是ds1 - 分表策略:
user_id % 16决定落入库内的哪张分表 - 主键生成:雪花算法保证分片环境下ID全局唯一
分片键选择:决定扩展上限的关键决策
分片键选错了,后续改造成本极高。选择原则:
- 高基数字段:user_id优于status(状态值只有几种,无法均匀分布)
- 查询高频字段:分片键必须是80%以上查询的WHERE条件
- 避免跨分片查询:如果按user_id分片但大量按order_id查询,每个请求都会广播到所有分片
// 分片键路由验证
// user_id = 12345
// 分库: 12345 % 2 = 1 → ds1
// 分表: 12345 % 16 = 9 → orders_9
// 最终路由: ds1.orders_9
// 非分片键查询(广播路由)
SELECT * FROM orders WHERE order_id = 'ORD20260729001'
// 会被路由到所有32个分片执行,性能灾难
// 解决方案:建立order_id到user_id的映射表
// 或使用基因法:将user_id的余数信息编码到order_id中
基因法实现:order_id由雪花ID+分片基因组成,从order_id可以直接推算出分片位置,无需映射表。
// 基因法ID生成
public class GeneShardingKeyGenerator {
// 分片数=16,基因占4bit
private static final int GENE_BITS = 4;
private static final int GENE_MASK = (1 << GENE_BITS) - 1;
public static long generateId(long snowflakeId, long userId) {
long gene = userId & GENE_MASK; // 取userId低4bit作为基因
return (snowflakeId << GENE_BITS) | gene; // 拼接到ID末尾
}
public static int extractShardIndex(long orderId) {
return (int) (orderId & GENE_MASK); // 从ID中提取分片位置
}
}
数据迁移:从单表到分片的平滑切换
已有线上数据的分片迁移是最危险的环节。推荐双写方案:
# 迁移步骤
# 1. 开启双写:新数据同时写入旧表和分片表
# 2. 历史数据迁移:使用DataX分批同步旧表到分片表
# 3. 数据校验:对比旧表和分片表数据一致性
# 4. 读流量切换:逐步将查询切换到分片表
# 5. 停止双写:确认稳定后关闭旧表写入
# DataX迁移任务配置(核心片段)
{
"job": {
"content": [{
"reader": {
"name": "mysqlreader",
"parameter": {
"connection": [{
"jdbcUrl": "jdbc:mysql://old-db:3306/order_db",
"query": ["SELECT * FROM orders WHERE id BETWEEN ${start} AND ${end}"]
}],
"fetchSize": 5000
}
},
"writer": {
"name": "mysqlwriter",
"parameter": {
"writeMode": "insert",
"batchSize": 2000
}
}
}],
"setting": {
"speed": {
"channel": 4,
"bytes": 1048576
}
}
}
}
迁移全程保持旧表可读写,任何异常随时回滚到旧表。校验通过后再切换读流量,逐步放量。完整的分片迁移窗口一般需要2-4周。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-fen-ku-fen-biao-shi-zhan-shardingsphere-chui-zhi-chai/