← 返回 MYSQL 列表

分布式 MySQL

分布式 MySQL

一、分库分表

1.1 什么情况下需要分库分表

场景 表现 解决方案
单表数据量过大 查询慢、DDL 锁表时间长 分表
单库并发过高 连接数不足、CPU 100% 分库
读写 QPS 超出上限 单机资源瓶颈 分库 + 读写分离
数据增长太快 备份恢复时间过长 分库分表

1.2 垂直分库

将不同的业务表拆分到不同的数据库实例。

垂直分库前:
  一个库:user + order + product + payment

垂直分库后:
  user_db:用户表
  order_db:订单表
  product_db:商品表
  payment_db:支付表

特点

  • 按业务领域拆分,天然解耦
  • 每个库可以使用不同的硬件配置
  • 跨库查询需要应用层处理

1.3 水平分库

将同一张表的数据分布到多个数据库实例。

水平分库(4 个库,按 user_id 取模):
  user_db_0:user_id % 4 = 0
  user_db_1:user_id % 4 = 1
  user_db_2:user_id % 4 = 2
  user_db_3:user_id % 4 = 3

分片键选择

分片键 优点 缺点
user_id 用户维度查询高效,数据分布均匀 跨用户查询困难
order_id 全局唯一,分布均匀 按用户查询需路由
time 范围查询友好 热点问题(最近数据集中)
hash 分布均匀 范围查询不支持

1.4 分库分表策略

sql
-- 策略1:根据 ID 范围分片
-- user_id 1~1000万 → db_0
-- user_id 1001万~2000万 → db_1

-- 策略2:根据哈希取模分片
-- user_id % 4 → db_0 ~ db_3

-- 策略3:根据时间分片
-- 2024年订单 → orders_2024
-- 2025年订单 → orders_2025

-- 策略4:一致性哈希
-- 减少扩容时的数据迁移量

二、中间件使用

2.1 ShardingSphere

ShardingSphere 是 Apache 基金会下的分布式数据库中间件生态。

ShardingSphere-JDBC(客户端模式):

yaml
# 分片规则配置(application.yaml)
spring:
  shardingsphere:
    datasource:
      names: ds0, ds1, ds2, ds3
      ds0:
        url: jdbc:mysql://192.168.1.100:3306/db_0
      ds1:
        url: jdbc:mysql://192.168.1.100:3306/db_1
      ds2:
        url: jdbc:mysql://192.168.1.101:3306/db_2
      ds3:
        url: jdbc:mysql://192.168.1.101:3306/db_3

    sharding:
      tables:
        t_order:
          actual_data_nodes: ds$->{0..3}.t_order_$->{0..15}
          table_strategy:
            inline:
              sharding_column: order_id
              algorithm_expression: t_order_$->{order_id % 16}
          database_strategy:
            inline:
              sharding_column: user_id
              algorithm_expression: ds$->{user_id % 4}

ShardingSphere-Proxy(服务端模式):

yaml
# server.yaml
rules:
  - !SHARDING
    tables:
      t_order:
        actualDataNodes: ds_${0..3}.t_order_${0..15}
        databaseStrategy:
          standard:
            shardingColumn: user_id
            shardingAlgorithmName: database_inline
        tableStrategy:
          standard:
            shardingColumn: order_id
            shardingAlgorithmName: table_inline

    shardingAlgorithms:
      database_inline:
        type: INLINE
        props:
          algorithm-expression: ds_${user_id % 4}
      table_inline:
        type: INLINE
        props:
          algorithm-expression: t_order_${order_id % 16}

2.2 MyCat

MyCat 是一个基于服务的分布式数据库中间件。

xml
<!-- schema.xml -->
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100">
    <table name="t_order" dataNode="dn1,dn2,dn3" rule="order_rule"/>
</schema>

<dataNode name="dn1" dataHost="host1" database="db_0" />
<dataNode name="dn2" dataHost="host2" database="db_1" />
<dataNode name="dn3" dataHost="host3" database="db_2" />

<dataHost name="host1" maxCon="100" minCon="10" balance="1"
          writeType="0" dbType="mysql" dbDriver="native">
    <writeHost host="hostM1" url="192.168.1.100:3306" user="root" password="123">
        <readHost host="hostS1" url="192.168.1.101:3306" user="root" password="123" />
    </writeHost>
</dataHost>

<!-- rule.xml -->
<tableRule name="order_rule">
    <rule>
        <columns>user_id</columns>
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <property name="count">3</property>
</function>

2.3 中间件对比

特性 ShardingSphere-JDBC ShardingSphere-Proxy MyCat
部署方式 嵌入应用 独立服务 独立服务
性能 高(无网络跳转) 中(有网络开销)
协议支持 JDBC MySQL 协议 MySQL 协议
分布式事务 XA、SEATA XA、SEATA 弱 XA
学习成本
适用场景 Java 应用 多语言、非 Java 传统 MySQL 代理

三、分片规则与路由

3.1 常用分片算法

java
// 1. 取模分片
public class ModShardingAlgorithm {
    public String doSharding(Collection<String> availableTargetNames, Long shardingValue) {
        int size = availableTargetNames.size();
        int index = (int)(shardingValue % size);
        // 路由到对应的目标
        return availableTargetNames.toArray()[index].toString();
    }
}

// 2. 范围分片
public class RangeShardingAlgorithm {
    public String doSharding(Long shardingValue) {
        if (shardingValue <= 10000000) return "ds_0";
        if (shardingValue <= 20000000) return "ds_1";
        return "ds_2";
    }
}

// 3. 时间分片
public class TimeShardingAlgorithm {
    public String doSharding(Date shardingValue) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(shardingValue);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        return String.format("ds_%d%02d", year, month);
    }
}

// 4. 一致性哈希
// 使用 TreeMap 实现环形哈希,减少扩容数据迁移

3.2 路由策略

java
// 标准路由(基于分片键)
public class StandardRouter {
    public DataNode route(String sql, ShardingValue shardingValue) {
        // SQL: SELECT * FROM t_order WHERE order_id = ?
        // 解析出 order_id → 计算分片 → 路由到具体节点

        int dbIndex = shardingValue % 4;        // 分库
        int tableIndex = shardingValue % 16;     // 分表
        return new DataNode("ds_" + dbIndex, "t_order_" + tableIndex);
    }
}

// 广播路由(没有分片键的查询)
public class BroadcastRouter {
    public List<String> route() {
        // SQL: SELECT COUNT(*) FROM t_order
        // 没有分片键需要发送到所有节点

        return Arrays.asList("ds_0", "ds_1", "ds_2", "ds_3");
    }
}

// 强制路由(Hint)
public class HintRouter {
    public DataNode route(int userId) {
        // 通过 Hint 指定分片键
        return new DataNode("ds_" + (userId % 4), "t_order");
    }
}

3.3 跨库跨表查询

sql
-- 场景1:查询用户的所有订单(分片键已知)
-- 根据 user_id 直接路由到具体分片
SELECT * FROM t_order WHERE user_id = 100;

-- 场景2:按订单号查询(分片键未知)
-- 方案A:使用映射表
SELECT user_id FROM order_user_mapping WHERE order_no = '20240101001';
-- 得到 user_id 后再路由

-- 方案B:广播查询(发到所有分片,合并结果)
SELECT * FROM t_order_0 WHERE order_no = '20240101001'
UNION ALL
SELECT * FROM t_order_1 WHERE order_no = '20240101001'
...

-- 场景3:聚合查询
-- 需要从所有分片查询后在中间件/应用层合并
-- SELECT COUNT(*) FROM t_order → 各分片分别COUNT → 汇总求和
-- SELECT * FROM t_order ORDER BY create_time LIMIT 10 → 各分片LIMIT → 合并排序

四、分布式事务

4.1 CAP 与 BASE 理论

CAP 理论:
  C(一致性):所有节点数据一致
  A(可用性):服务总是可用
  P(分区容错性):节点间通信失败仍能工作
  三者只能同时满足其二

BASE 理论(最终一致性):
  BA(基本可用):系统基本可用
  S(软状态):状态可以不一致
  E(最终一致性):最终数据会一致

在分布式数据库场景:
  通常选择 AP(可用性 + 分区容错性)
  通过最终一致性保证数据一致

4.2 XA 事务(强一致性)

sql
-- XA 两阶段提交

-- 阶段1:准备
XA START 'xid1';
UPDATE account SET balance = balance - 100 WHERE id = 1;
XA END 'xid1';
XA PREPARE 'xid1';

XA START 'xid2';
UPDATE account SET balance = balance + 100 WHERE id = 2;
XA END 'xid2';
XA PREPARE 'xid2';

-- 阶段2:提交
XA COMMIT 'xid1';
XA COMMIT 'xid2';

-- 如果某个节点 PREPARE 失败,全部回滚
XA ROLLBACK 'xid1';
XA ROLLBACK 'xid2';

4.3 柔性事务(Seata AT)

java
// Seata AT 模式(自动补偿)

@GlobalTransactional
public void createOrder(OrderDTO order) {
    // 1. 扣减库存(DB1)
    inventoryService.deduct(order.getProductId(), order.getQuantity());

    // 2. 创建订单(DB2)
    orderService.create(order);

    // 3. 扣减余额(DB3)
    accountService.debit(order.getUserId(), order.getAmount());

    // 所有操作成功 → 全局提交
    // 任何操作失败 → 全局回滚(自动执行补偿 SQL)
}

4.4 事务方案对比

方案 一致性 性能 实现复杂度 适用场景
XA 强一致性 重要交易(跨数据库)
Seata AT 最终一致性 微服务分布式事务
TCC 最终一致性 需要灵活控制
本地消息表 最终一致性 异步消息场景
可靠消息(RocketMQ) 最终一致性 高并发最终一致

五、实战案例

案例:订单系统分库分表设计

sql
-- 需求:订单系统,预计日订单量 100 万,年订单量 3.6 亿

-- 分片设计:
-- 分库数量:8(ds_0 ~ ds_7)
-- 分表数量:每个库 16 张(t_order_0 ~ t_order_15)
-- 总分片数:8 × 16 = 128
-- 每个分片承载:3.6亿 / 128 ≈ 280 万行

-- 分片键:user_id(按用户查询是主要场景)

-- 路由规则:
-- 库序号:user_id % 8
-- 表序号:user_id / 8 % 16

-- Java 实现路由
public class OrderRouter {
    private static final int DB_COUNT = 8;
    private static final int TABLE_COUNT = 16;

    public String getTableName(Long userId) {
        int dbIndex = (int)(userId % DB_COUNT);
        int tableIndex = (int)((userId / DB_COUNT) % TABLE_COUNT);
        return String.format("ds_%d.t_order_%d", dbIndex, tableIndex);
    }
}

-- 创建分表的模板(脚本生成 128 张表)
-- 每张表结构相同
CREATE TABLE `t_order_0` (
    `order_id`    BIGINT      NOT NULL COMMENT '订单ID',
    `order_no`    VARCHAR(32) NOT NULL COMMENT '订单号',
    `user_id`     BIGINT      NOT NULL COMMENT '用户ID',
    `amount`      DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    `status`      TINYINT     NOT NULL DEFAULT 0,
    `create_time` DATETIME    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`order_id`),
    INDEX `idx_user_id` (`user_id`),
    INDEX `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 映射表(解决按订单号查询)
CREATE TABLE `order_user_mapping` (
    `order_no` VARCHAR(32) PRIMARY KEY,
    `user_id` BIGINT NOT NULL,
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

总结

知识点 核心要点
垂直分库 按业务拆分到独立实例
水平分库 按分片键拆到多个实例
分片策略 取模、范围、时间、一致性哈希
中间件 ShardingSphere-JDBC(高性能)、ShardingSphere-Proxy(多语言)、MyCat
跨库查询 映射表、广播查询、中间件聚合
分布式事务 XA 强一致、Seata 最终一致、TCC、消息表
核心挑战 分片键选择、跨分片查询、数据迁移、分布式事务