MySQL分库分表

分库分表是应对MySQL单机单库数据量过大、性能瓶颈的重要解决方案。当数据库中的数据量达到一定规模(通常千万级以上)或并发访问量过高时,单机数据库会面临存储容量不足、查询性能下降、IO压力过大等问题。分库分表通过将数据分散到多个数据库或表中,实现数据的水平扩展,提升系统的吞吐量和可用性。

一、分库分表的基本概念

1. 什么是分库分表

- 分表:将一个大表按照某种规则拆分成多个小表,每个小表存储一部分数据

- 分库:将一个大数据库按照某种规则拆分成多个小数据库,每个小数据库存储一部分数据

- 通常分库和分表会结合使用,形成分布式数据库架构

2. 分库分表的目的

- 解决单表数据量过大问题,提高查询性能

- 分散数据库压力,提高并发处理能力

- 突破单机存储容量限制,支持数据规模增长

- 提高系统可用性,单个分库或分表故障不影响整体系统

3. 分库分表的挑战

- 分布式事务一致性问题

- 跨库联合查询复杂

- 全局唯一ID生成困难

- 数据迁移和扩容复杂

- 应用程序需要适配分库分表逻辑

二、分库分表的方式

1. 水平拆分(Horizontal Sharding)

水平拆分是将表中的行按照某种规则分散到不同的表或库中,每个表或库的结构相同,但数据不同。

(1)特点

- 每个分表/分库的结构与原表完全一致

- 数据按行拆分,不改变表结构

- 适用于数据量过大的表,可无限扩展(理论上)

(2)常见拆分规则

① 范围拆分:

按时间、ID等范围进行拆分,如按年份拆分订单表:

- orders_2021:存储2021年的订单

- orders_2022:存储2022年的订单

- orders_2023:存储2023年的订单

② 哈希拆分:

对关键字段进行哈希计算,根据哈希结果分配到不同分表,如按用户ID哈希:

table_index = user_id % 4

将用户ID哈希后模4,分配到users_0、users_1、users_2、users_3四个表中

③ 地理位置拆分:

按用户所在地区拆分,如按省份拆分用户表:

- users_beijing、users_shanghai、users_guangdong等

④ 业务维度拆分:

按业务线或客户类型拆分,如电商系统按客户等级拆分:

- users_vip、users_normal、users_new等

2. 垂直拆分(Vertical Sharding)

垂直拆分是将表中的列按照某种规则分散到不同的表或库中,每个表或库的结构不同,但共同组成原表的完整数据。

(1)特点

- 每个分表/分库的结构不同,包含原表的部分字段

- 数据按列拆分,拆分后表结构发生变化

- 适用于表字段过多或不同字段访问频率差异大的场景

(2)常见拆分规则

① 按访问频率拆分:

将常用字段和不常用字段拆分为不同表:

- users_basic:存储用户ID、姓名、手机号等常用信息

- users_extra:存储用户简介、爱好、注册IP等不常用信息

② 按字段大小拆分:

将大字段拆分到单独表中,如:

- articles:存储文章ID、标题、发布时间等

- articles_content:存储文章ID和正文内容(大字段)

③ 按业务职责拆分:

将不同业务模块的数据拆分到不同库中,如电商系统:

- user_db:用户相关表

- order_db:订单相关表

- product_db:商品相关表

- payment_db:支付相关表

3. 混合拆分

实际应用中,通常会结合水平拆分和垂直拆分:

① 先进行垂直拆分,按业务模块拆分不同的库

② 对每个库中数据量大的表再进行水平拆分

例如:电商系统先垂直拆分为用户库、订单库、商品库,然后对订单库中的订单表按时间进行水平拆分

三、分库分表的实现方案

1. 客户端解决方案(代码层分片)

在应用程序代码中实现分库分表逻辑,直接操作多个数据库实例。

(1)实现方式

- 自定义分片逻辑:在DAO层根据分片规则路由到相应的库和表

- 使用ORM框架扩展:如MyBatis的插件机制实现分库分表

(2)示例代码(伪代码)
// 哈希分片示例
public class OrderDAO {
    // 订单分表数量
    private static final int TABLE_COUNT = 8;
    
    public Order getOrder(Long orderId) {
        // 计算分表索引
        int tableIndex = (int)(orderId % TABLE_COUNT);
        String tableName = "orders_" + tableIndex;
        
        // 执行查询
        return jdbcTemplate.queryForObject(
            "SELECT * FROM " + tableName + " WHERE id = ?",
            new Object[]{orderId},
            new OrderRowMapper()
        );
    }
    
    public void saveOrder(Order order) {
        // 计算分表索引
        int tableIndex = (int)(order.getId() % TABLE_COUNT);
        String tableName = "orders_" + tableIndex;
        
        // 执行插入
        jdbcTemplate.update(
            "INSERT INTO " + tableName + " (id, user_id, amount) VALUES (?, ?, ?)",
            order.getId(), order.getUserId(), order.getAmount()
        );
    }
}
(3)优缺点

优点:

- 实现简单,无需引入额外组件

- 性能较好,无中间层开销

- 灵活性高,可根据业务定制分片规则

缺点:

- 分片逻辑与业务代码耦合

- 维护成本高,修改分片规则需修改代码

- 跨库事务和联合查询实现复杂

- 不支持动态扩容

2. 中间件解决方案

通过独立的中间件实现分库分表逻辑,应用程序通过中间件访问数据库,无需关心分片细节。

(1)主流中间件

① ShardingSphere(开源):

- 包含Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar

- 功能全面,支持多种分片策略、分布式事务、读写分离等

- 社区活跃,文档丰富

② MyCat(开源):

- 基于Cobar开发,成熟稳定

- 支持多种分片规则,提供监控功能

- 采用代理模式,对应用透明

③ 其他中间件:

- Atlas(奇虎360开源)

- DDB(阿里云分布式数据库服务)

- TDSQL(腾讯分布式数据库)

(2)Sharding-JDBC配置示例
// application.yml 配置
spring:
  shardingsphere:
    datasource:
      names: db0, db1
      db0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/db0
        username: root
        password: 123456
      db1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/db1
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          t_order:
            actual-data-nodes: db${0..1}.t_order${0..3}
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: order_db_inline
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: order_table_inline
        sharding-algorithms:
          order_db_inline:
            type: INLINE
            props:
              algorithm-expression: db${user_id % 2}
          order_table_inline:
            type: INLINE
            props:
              algorithm-expression: t_order${order_id % 4}
    props:
      sql-show: true
(3)优缺点

优点:

- 分片逻辑与业务代码解耦

- 功能丰富,支持复杂场景

- 便于统一管理和维护

- 支持动态扩容和配置变更

缺点:

- 引入中间件增加系统复杂度

- 存在一定性能开销(代理模式)

- 学习和配置成本较高

- 部分复杂SQL可能不支持

3. 数据库厂商解决方案

一些数据库厂商提供了原生的分库分表支持:

- MySQL NDB Cluster:MySQL官方集群方案,支持自动分片

- 阿里云PolarDB:兼容MySQL,支持自动分库分表

- 腾讯云TDSQL:基于MySQL的分布式数据库

- 华为云GaussDB:支持分布式部署和自动分片

四、分库分表的关键问题

1. 全局唯一ID生成

分库分表后,自增ID无法保证全局唯一,需要分布式ID生成方案:

① UUID/GUID:

String id = UUID.randomUUID().toString().replaceAll("-", "");
// 生成类似:550e8400e29b41d3a476d96b66d08491

优点:实现简单,无需中心化服务

缺点:无序,长度长,索引性能差

② 雪花算法(Snowflake):

64位ID,包含时间戳、机器ID、序列号等信息,有序递增

// 伪代码
public class SnowflakeIdGenerator {
    private final long workerId;         // 机器ID
    private final long datacenterId;     // 数据中心ID
    private long sequence = 0L;          // 序列号
    private long lastTimestamp = -1L;    // 上次生成ID的时间戳
    
    // 生成ID的方法
    public synchronized long nextId() {
        // 实现逻辑...
    }
}

优点:有序,性能高,可手动设置机器ID

缺点:依赖系统时钟,时钟回拨会导致ID重复

③ 数据库自增ID表:

-- 创建ID生成表
CREATE TABLE sequence (
    name VARCHAR(50) NOT NULL PRIMARY KEY,
    value BIGINT NOT NULL
);

-- 插入初始值
INSERT INTO sequence (name, value) VALUES ('order_id', 1);

-- 获取下一个ID
REPLACE INTO sequence (name, value) VALUES ('order_id', LAST_INSERT_ID(value + 1));
SELECT LAST_INSERT_ID();

优点:实现简单,ID有序递增

缺点:单点故障风险,性能瓶颈

④ 其他方案:

- Redis自增:利用INCR命令生成ID

- 号段模式:预分配一段ID,减少数据库访问

2. 分布式事务

分库分表后,跨库操作需要保证事务一致性:

① 两阶段提交(2PC):

通过协调者协调所有参与者,分准备和提交两个阶段完成事务

优点:强一致性

缺点:性能差,存在阻塞风险,协调者单点问题

② 补偿事务(TCC):

将事务拆分为Try、Confirm、Cancel三个操作,通过补偿机制保证最终一致性

优点:性能好,无锁阻塞

缺点:业务侵入性强,实现复杂

③ 本地消息表:

通过本地事务记录消息,异步通知其他服务,保证最终一致性

优点:实现简单,可靠性高

缺点:有一定延迟,需要处理消息重复问题

④ SAGA模式:

将长事务拆分为多个短事务,每个短事务有对应的补偿事务

优点:适合长事务场景,可扩展性好

缺点:实现复杂,一致性保障较弱

⑤ 中间件方案:

ShardingSphere支持XA事务和SEATA事务,简化分布式事务实现

3. 跨库联合查询

分库分表后,JOIN查询变得复杂,解决方案包括:

① 避免跨库JOIN:通过合理设计分片规则,将相关数据放在同一库表

② 应用层聚合:先查询各分库数据,再在应用程序中进行JOIN操作

// 伪代码:应用层聚合
List orders = orderDAO.queryByUserId(userId);  // 查询用户所有订单
for (Order order : orders) {
    // 查询每个订单的商品信息
    List products = productDAO.queryByOrderId(order.getId());
    order.setProducts(products);
}
return orders;

③ 数据冗余:适当冗余数据,减少JOIN需求

④ 中间件支持:部分中间件(如ShardingSphere)支持跨库JOIN

4. 数据迁移与扩容

分库分表后的数据迁移和扩容需要特别处理:

① 停机迁移:

停止服务,迁移数据,修改配置,重启服务

优点:简单可靠

缺点:服务中断,影响可用性

② 在线迁移:

通过工具实现不停机数据迁移,如:

- Percona XtraBackup:热备份工具

- ShardingSphere Migration:数据迁移工具

- 自定义迁移程序:双写数据,校验一致性后切换

③ 平滑扩容:

采用一致性哈希等策略,减少扩容时的数据迁移量

例如:从4个分表扩容到8个分表,只需迁移一半数据

五、分库分表的最佳实践

1. 拆分策略选择

- 优先考虑垂直拆分,按业务边界划分数据库

- 水平拆分选择合适的分片键,应满足:

1. 访问频率高,大多数查询都包含该字段

2. 分布均匀,避免数据倾斜

3. 相对稳定,不易变化

- 避免多层级拆分,控制分库分表的复杂度

2. 拆分粒度设计

- 分表数量:单表数据量控制在1000万以内,查询性能较好

- 分库数量:根据服务器资源和并发量确定,避免过多分库增加管理成本

- 预留扩容空间:初始设计时考虑未来3-5年的数据增长

3. 应用适配

- 避免使用SELECT *,只查询需要的字段

- 优化分页查询,分库分表环境下COUNT(*)和LIMIT分页代价高

- 避免复杂SQL,如多表JOIN、子查询等

- 批量操作尽量按分片键拆分,避免全库扫描

4. 监控与运维

- 监控各分库分表的容量、性能和负载均衡情况

- 定期检查数据分布,避免数据倾斜

- 建立完善的备份和恢复机制

- 记录分库分表的元数据信息,便于管理

5. 循序渐进

- 不是所有表都需要分库分表,只拆分数据量大或访问频繁的表

- 可以先单库分表,再逐步过渡到分库分表

- 小步迭代,每次拆分后进行充分测试

6. 工具选型

- 中小规模应用:可考虑客户端分片或轻量级中间件

- 大规模复杂应用:建议使用成熟的中间件如ShardingSphere

- 云环境:优先考虑云厂商提供的分布式数据库服务

六、分库分表示例场景

1. 电商订单系统

① 垂直拆分:

- 订单库(order_db):存储订单基本信息

- 订单明细表(order_item_db):存储订单商品明细

- 订单支付库(order_payment_db):存储支付相关信息

② 水平拆分:

订单表按用户ID哈希分8个库,每个库再按时间分12个表:

- order_db_0 至 order_db_7(8个分库)

- 每个分库包含 orders_202301 至 orders_202312(12个分表)

③ 分片键选择:

- 分库键:user_id(保证同一用户的订单在同一库)

- 分表键:create_time(按月份拆分)

2. 社交平台用户系统

① 垂直拆分:

- 用户基本信息库(user_basic_db)

- 用户资料库(user_profile_db)

- 用户关系库(user_relation_db):关注、粉丝等关系

② 水平拆分:

用户表按用户ID范围分4个库,每个库分16个表:

- user_basic_db_0 至 user_basic_db_3

- 每个库包含 user_00 至 user_15

③ 分片键选择:

- 分库分表键:user_id(便于按用户ID直接定位)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值