MySQL分库分表,从“一室一厅”到“豪华别墅区”的数据库升级之旅

大家好,我是小悟。

一、分库分表是什么?------数据库的"甜蜜烦恼"

把你的MySQL数据库想象成是个小单身公寓(单库单表):

  • 刚开始住进去时,东西不多,找啥都方便
  • 后来你结婚了(用户量增加),生了娃(数据量暴增),还养了条二哈(业务复杂了)
  • 现在全家挤在小公寓里,每天早上一家人抢厕所(数据库锁竞争),找双袜子要翻遍全家(全表扫描)
  • 邻居天天投诉你家太吵(性能影响其他服务)

这时候你需要: 分库 :买下整栋楼!把不同的家人安排在不同楼层(业务垂直拆分) 分表:把每个房间再隔成小隔间!把袜子、内裤、外套分开放(数据水平拆分)

二、详细实施步骤------从"蜗居"到"豪宅"的装修指南

第1步:设计蓝图------想清楚再动手

sql 复制代码
-- 先看看现在的"房子"有多大
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步:垂直分库------让专业的人住专业的楼层

sql 复制代码
-- 原来都挤在一个库里
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步:水平分表------每个房间都装上帝视角衣柜

方法一:按范围分表(适合时间序列)

sql 复制代码
-- 订单表按月份拆分,再也不怕找去年的订单像考古了
-- 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 (
    -- 结构同上
);

方法二:按哈希分表(均匀分布)

sql 复制代码
-- 用户表按ID取模分表,16张表够不够?
-- 先创建用户表模板
DELIMITER $$

CREATE PROCEDURE create_user_tables()
BEGIN
    DECLARE i INT DEFAULT 0;
    WHILE i < 16 DO
        SET @table_name = CONCAT('user_villa.user_info_', LPAD(i, 2, '0'));
        SET @sql = CONCAT('
            CREATE TABLE IF NOT EXISTS ', @table_name, ' (
                user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
                username VARCHAR(50) NOT NULL,
                email VARCHAR(100),
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_username (username)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ');
        PREPARE stmt FROM @sql;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
        SET i = i + 1;
    END WHILE;
END$$

DELIMITER ;

-- 执行创建
CALL create_user_tables();

第4步:路由策略------给每个数据配个"导航系统"

typescript 复制代码
// 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步:中间件配置------请个"万能管家"

yaml 复制代码
# 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步:数据迁移------搬家不能丢东西

sql 复制代码
-- 第一步:先抄家(备份)
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生成------给每个数据发身份证

ini 复制代码
// 雪花算法: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;
    }
    
    // 全局唯一,趋势递增,适合分库分表
}

三、注意事项------别墅区的物业管理条例

  1. 分布式事务:楼上转账,楼下扣款,必须同时成功或失败

    java 复制代码
    // 使用Seata等分布式事务解决方案
    @GlobalTransactional
    public void transferMoney(long fromUser, long toUser, BigDecimal amount) {
        // 1. 从用户库扣款
        // 2. 向订单库插入记录
        // 要么都成功,要么都回滚
    }
  2. 跨表查询:想找张三的所有订单?得问遍所有表!

    sql 复制代码
    -- 分表前:一步到位
    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
    -- ... 查遍所有月份表
  3. JOIN操作:用户表和订单表在不同库?只能业务层JOIN

    ini 复制代码
    // 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)是你的健康检测仪。

接受不完美:分库分表后,事务复杂了,查询麻烦了,运维困难了。但这就是成长的代价,就像长大后发现世界不是非黑即白。

最后提醒:分库分表前,先试试这些"减肥方法":

  1. 加索引(给东西贴标签)
  2. 优化SQL(整理收纳技巧)
  3. 升级硬件(换个大房子)
  4. 读写分离(男女分开用厕所)

只有当这些都不够用时,才考虑分库分表这个大工程。记住,好的架构是演进而来的,不是设计出来的。就像好的婚姻,需要慢慢磨合,不能一开始就分房睡(分库)还分床(分表)!

祝你的数据库从"小公寓"顺利升级到"豪华别墅区",住得宽敞,查得飞快!

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

相关推荐
㳺三才人子3 小时前
初探 Flask
后端·python·flask·html
星栈独行3 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下3 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.4 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易4 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0074 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶4 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
IronMurphy5 小时前
MySQL拷打第二讲
数据库·mysql
ltl5 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬5 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar