大家好,我是小悟。
一、分库分表是什么?——数据库的“甜蜜烦恼”
把你的MySQL数据库想象成是个小单身公寓(单库单表):
- 刚开始住进去时,东西不多,找啥都方便
- 后来你结婚了(用户量增加),生了娃(数据量暴增),还养了条二哈(业务复杂了)
- 现在全家挤在小公寓里,每天早上一家人抢厕所(数据库锁竞争),找双袜子要翻遍全家(全表扫描)
- 邻居天天投诉你家太吵(性能影响其他服务)
这时候你需要: 分库:买下整栋楼!把不同的家人安排在不同楼层(业务垂直拆分) 分表:把每个房间再隔成小隔间!把袜子、内裤、外套分开放(数据水平拆分)
二、详细实施步骤——从“蜗居”到“豪宅”的装修指南
第1步:设计蓝图——想清楚再动手
-- 先看看现在的“房子”有多大 SELECT TABLE_SCHEMA as '数据库', TABLE_NAME as '表名', ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) as '大小(MB)', TABLE_ROWS as '行数' FROM information_schema.TABLES WHERE TABLE_SCHEMA = '你的数据库名' ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC; -- 思考人生三连问: -- 1. 按业务分?(用户相关、订单相关、商品相关) -- 2. 按时间分?(2024订单、2025订单) -- 3. 按地域分?(北京用户、上海用户)
第2步:垂直分库——让专业的人住专业的楼层
-- 原来都挤在一个库里 CREATE DATABASE single_apartment; -- 现在买栋楼,每层一个专业户 CREATE DATABASE user_villa; -- 用户专属楼层 CREATE DATABASE order_mansion; -- 订单豪华层 CREATE DATABASE product_tower; -- 商品展示层 -- 用户表搬到用户楼层 CREATE TABLE user_villa.user_info ( user_id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 用户相关的其他字段... ); -- 订单表搬到订单楼层 CREATE TABLE order_mansion.order_info ( order_id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, amount DECIMAL(10, 2), status TINYINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 订单相关的其他字段... );
第3步:水平分表——每个房间都装上帝视角衣柜
方法一:按范围分表(适合时间序列)
-- 订单表按月份拆分,再也不怕找去年的订单像考古了 -- 2024年订单表 CREATE TABLE order_mansion.order_202401 ( order_id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, -- ... 其他字段 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_created_at (created_at) ) PARTITION BY RANGE (MONTH(created_at)) ( PARTITION p1 VALUES LESS THAN (2), PARTITION p2 VALUES LESS THAN (3), -- ... 更多分区 ); -- 2024年2月订单表 CREATE TABLE order_mansion.order_202402 ( -- 结构同上 );
方法二:按哈希分表(均匀分布)
-- 原来都挤在一个库里 CREATE DATABASE single_apartment; -- 现在买栋楼,每层一个专业户 CREATE DATABASE user_villa; -- 用户专属楼层 CREATE DATABASE order_mansion; -- 订单豪华层 CREATE DATABASE product_tower; -- 商品展示层 -- 用户表搬到用户楼层 CREATE TABLE user_villa.user_info ( user_id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 用户相关的其他字段... ); -- 订单表搬到订单楼层 CREATE TABLE order_mansion.order_info ( order_id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, amount DECIMAL(10, 2), status TINYINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 订单相关的其他字段... );
第4步:路由策略——给每个数据配个“导航系统”
// Java代码示例:数据路由导航 public class ShardingNavigator { // 用户表路由:根据user_id决定去哪个表 public static String getUserTableName(long userId) { int tableIndex = (int) (userId % 16); return String.format("user_info_%02d", tableIndex); } // 订单表路由:根据时间决定去哪个表 public static String getOrderTableName(Date orderTime) { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); return "order_" + sdf.format(orderTime); } // 组合查询:跨表查询就像组织家庭聚会 public List<Order> getUserOrders(long userId, Date startTime, Date endTime) { List<Order> allOrders = new ArrayList<>(); Calendar calendar = Calendar.getInstance(); calendar.setTime(startTime); // 遍历时间范围内的所有月份表 while (calendar.getTime().before(endTime)) { String tableName = getOrderTableName(calendar.getTime()); String sql = "SELECT * FROM " + tableName + " WHERE user_id = ?"; // 执行查询并添加到结果集 // ... calendar.add(Calendar.MONTH, 1); } return allOrders; } }
第5步:中间件配置——请个“万能管家”
# ShardingSphere配置文件示例(YAML格式) # 这位管家知道所有房间在哪 dataSources: ds0: url: jdbc:mysql://localhost:3306/user_villa?useSSL=false username: root password: your_password ds1: url: jdbc:mysql://localhost:3307/order_mansion?useSSL=false username: root password: your_password shardingRule: tables: user_info: actualDataNodes: ds0.user_info_${0..15} tableStrategy: inline: shardingColumn: user_id algorithmExpression: user_info_${user_id % 16} order_info: actualDataNodes: ds1.order_${2024..2025}${1..12} tableStrategy: standard: shardingColumn: created_at preciseAlgorithmClassName: com.example.TimeShardingAlgorithm bindingTables: - user_info,order_info
第6步:数据迁移——搬家不能丢东西
-- 第一步:先抄家(备份) CREATE TABLE order_mansion.order_202401_new LIKE order_mansion.order_202401; -- 第二步:慢慢搬(增量迁移) INSERT INTO order_mansion.order_202401_new SELECT * FROM single_apartment.orders WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01'; -- 第三步:检查有没有落下的 SELECT (SELECT COUNT(*) FROM single_apartment.orders WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01') as old_count, (SELECT COUNT(*) FROM order_mansion.order_202401_new) as new_count; -- 第四步:切换门牌号(重命名) RENAME TABLE order_mansion.order_202401 TO order_mansion.order_202401_backup, order_mansion.order_202401_new TO order_mansion.order_202401;
第7步:全局ID生成——给每个数据发身份证
// 雪花算法:Twitter出品,必属精品 public class SnowflakeIdGenerator { private final long twepoch = 1288834974657L; private final long workerIdBits = 5L; private final long datacenterIdBits = 5L; private final long sequenceBits = 12L; private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException("时间倒流了,检查系统时间!"); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & 4095; // 2^12-1 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - twepoch) << 22) | (datacenterId << 17) | (workerId << 12) | sequence; } // 全局唯一,趋势递增,适合分库分表 }
三、注意事项——别墅区的物业管理条例
- 分布式事务:楼上转账,楼下扣款,必须同时成功或失败
// 使用Seata等分布式事务解决方案 @GlobalTransactional public void transferMoney(long fromUser, long toUser, BigDecimal amount) { // 1. 从用户库扣款 // 2. 向订单库插入记录 // 要么都成功,要么都回滚 }
- 跨表查询:想找张三的所有订单?得问遍所有表!
-- 分表前:一步到位 SELECT * FROM orders WHERE user_id = 123; -- 分表后:变成侦查行动 SELECT * FROM order_202401 WHERE user_id = 123 UNION ALL SELECT * FROM order_202402 WHERE user_id = 123 UNION ALL -- ... 查遍所有月份表
- JOIN操作:用户表和订单表在不同库?只能业务层JOIN
// 1. 先从用户库查用户 List<User> users = userDao.getUsers(condition); List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList()); // 2. 再去订单库查这些用户的订单 List<Order> orders = orderDao.getOrdersByUserIds(userIds); // 3. 在内存中组装 Map<Long, List<Order>> userOrdersMap = groupOrdersByUser(orders);
四、总结
分库分表就像数据库的成人礼,意味着你的业务从“小打小闹”变成了“正经事业”。但是:
不要过早优化:如果你的数据还没到百万级别,别急着分表,就像不能因为将来可能变胖,现在就买XXL号裤子。
选择合适的策略:按时间分?按地域分?按业务分?这就像选择衣柜整理方式,有人喜欢按季节,有人喜欢按颜色,适合的才是最好的。
准备好工具:中间件(ShardingSphere、MyCat)是你的瑞士军刀,监控工具(Prometheus、Grafana)是你的健康检测仪。
接受不完美:分库分表后,事务复杂了,查询麻烦了,运维困难了。但这就是成长的代价,就像长大后发现世界不是非黑即白。
最后提醒:分库分表前,先试试这些“减肥方法”:
- 加索引(给东西贴标签)
- 优化SQL(整理收纳技巧)
- 升级硬件(换个大房子)
- 读写分离(男女分开用厕所)
只有当这些都不够用时,才考虑分库分表这个大工程。记住,好的架构是演进而来的,不是设计出来的。就像好的婚姻,需要慢慢磨合,不能一开始就分房睡(分库)还分床(分表)!
祝你的数据库从“小公寓”顺利升级到“豪华别墅区”,住得宽敞,查得飞快!
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海