找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1072

积分

0

好友

153

主题
发表于 5 天前 | 查看: 14| 回复: 0

分库分表是应对海量数据与高并发访问的核心数据库架构方案,旨在突破单机数据库在性能、存储与连接数上的瓶颈。

一、 分库分表的核心概念

  • 分库:将数据分布到不同的数据库实例中。
  • 分表:将数据分布到同一数据库实例的不同表中。
  • 核心目标:解决单机数据库的存储上限与性能瓶颈。

二、 为何需要分库分表?

单机数据库的典型瓶颈
// 主要问题集中在:
// 1. 存储瓶颈:数据量超过服务器磁盘容量
// 2. 性能瓶颈:高并发下CPU、内存、IO不堪重负
// 3. 连接数限制:数据库连接数有限制
// 4. 运维困难:大数据量下的备份、恢复操作极其耗时

具体衡量指标

  • 数据量:单表数据量达到千万级别后,查询性能显著下降。
  • 并发量:查询每秒(QPS)高达数千时,单机数据库难以支撑。
  • 数据大小:单个数据库的数据量超过服务器磁盘总容量的70%。

三、 分库分表的四种类型

1. 垂直分库

根据业务模块进行拆分,将不同业务的表独立到不同的数据库中。

-- 拆分前:单库 `ecommerce`
-- 包含:users, products, orders, payments 等表

-- 拆分后:
-- 数据库 `user_db`:users表
-- 数据库 `product_db`:products表
-- 数据库 `order_db`:orders, payments表

优点:业务解耦,降低单库压力。
缺点:无法解决单表数据量过大的问题,跨库查询变得复杂。

2. 垂直分表

将一张宽表按列(字段)拆分,将频繁查询的列与不常用的大字段列分离。

-- 原表
CREATE TABLE users (
    id BIGINT,
    username VARCHAR(50),
    password VARCHAR(100),
    email VARCHAR(100),
    profile_text TEXT,     -- 不常用的大字段
    avatar LONGBLOB,       -- 图片数据
    created_time DATETIME
);

-- 拆分后
-- 基础信息表(高频查询)
CREATE TABLE users_base (
    id BIGINT,
    username VARCHAR(50),
    password VARCHAR(100),
    email VARCHAR(100),
    created_time DATETIME
);
-- 扩展信息表(低频查询)
CREATE TABLE users_ext (
    user_id BIGINT,
    profile_text TEXT,
    avatar LONGBLOB
);

优点:提升高频查询性能,避免IO浪费。
缺点:需要关联查询,增加了事务一致性的复杂度。

3. 水平分库

将同一张表的数据,按照某种规则分布到不同的数据库实例中。

-- 按用户ID取模分库(例如分为2个库,4张表)
-- 数据库:user_db_0
--   ├── users_0 (user_id % 4 = 0)
--   └── users_1 (user_id % 4 = 1)
-- 数据库:user_db_1
--   ├── users_2 (user_id % 4 = 2)
--   └── users_3 (user_id % 4 = 3)
4. 水平分表

将同一张表的数据,按照某种规则分布到同一数据库实例的不同表中。

-- 按用户ID取模分表(在同一数据库内)
-- 数据库:user_db
--   ├── users_0 (user_id % 4 = 0)
--   ├── users_1 (user_id % 4 = 1)
--   ├── users_2 (user_id % 4 = 2)
--   └── users_3 (user_id % 4 = 3)

四、 核心分片策略(路由算法)

1. 范围分片

按字段值的范围(如时间、ID区间)进行分片。

public class RangeSharding {
    // 按用户ID范围分片
    public String getTableName(Long userId) {
        if (userId >= 1 && userId <= 1000000) {
            return “users_0”;
        } else if (userId > 1000000 && userId <= 2000000) {
            return “users_1”;
        } else {
            return “users_2”;
        }
    }

    // 按时间范围分片(按月分表)
    public String getTableName(Date createTime) {
        SimpleDateFormat sdf = new SimpleDateFormat(”yyyy_MM”);
        return “orders_” + sdf.format(createTime);
    }
}

优点:易于扩展,支持高效的范围查询。
缺点:可能产生数据热点,导致负载不均。

2. 哈希分片

根据字段的哈希值取模来决定数据位置。

public class HashSharding {
    // 基于用户ID的哈希取模分片
    public String getTableName(Long userId, int tableCount) {
        int hash = Math.abs(userId.hashCode());
        int tableIndex = hash % tableCount;
        return “users_” + tableIndex;
    }
}

优点:数据分布相对均匀。
缺点:扩容时数据迁移量大,范围查询困难。

3. 地理位置分片

根据业务属性(如所属地区)进行分片。

public class GeoSharding {
    public String getTableName(String province) {
        switch (province) {
            case “北京”: case “天津”: case “河北”:
                return “users_north”;
            case “上海”: case “江苏”: case “浙江”:
                return “users_east”;
            case “广东”: case “广西”: case “海南”:
                return “users_south”;
            default:
                return “users_west”;
        }
    }
}

五、 挑战与解决方案

1. 分布式ID生成

使用雪花算法(Snowflake)生成全局唯一、趋势递增的ID。

@Component
public class SnowflakeIdGenerator {
    // ... 雪花算法实现细节(数据中心ID、机器ID、序列号等)
    public synchronized long nextId() {
        // 核心逻辑:结合时间戳、机器标识和序列号生成ID
        long timestamp = System.currentTimeMillis();
        // ... 处理时钟回拨、同一毫秒内序列号递增等逻辑
        return ((timestamp - 1609459200000L) << 22) |
               (datacenterId << 17) |
               (machineId << 12) |
               sequence;
    }
}
2. 跨库查询与JOIN

在应用层进行数据聚合,替代数据库层的JOIN。

@Service
public class OrderService {
    public OrderDetail getOrderDetail(Long orderId, Long userId) {
        // 1. 查询订单主信息
        Order order = orderMapper.selectById(orderId);
        // 2. 根据分片键(userId)定位到正确的库/表查询用户信息
        User user = userMapper.selectByShardingKey(userId);
        // 3. 查询订单商品列表
        List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
        // 4. 在应用层组装结果
        return new OrderDetail(order, user, items);
    }
}
3. 分布式事务

借助如Seata这类中间件,实现跨数据库的事务一致性。

@GlobalTransactional // Seata的全局事务注解
@Service
public class OrderCreateService {
    public void createOrder(Order order) {
        // 1. 扣减库存(操作商品库)
        productService.reduceStock(order.getProductId(), order.getQuantity());
        // 2. 创建订单(操作订单库)
        orderMapper.insert(order);
        // 3. 增加用户积分(操作用户库)
        userService.addPoints(order.getUserId(), order.getAmount());
    }
}
4. 分页查询

并行查询各分片,在内存中进行结果合并、排序与分页。

@Service
public class UserQueryService {
    public PageResult<User> queryUsers(int page, int size) {
        // 1. 并行查询所有分片,各取 (page+1)*size 条数据
        List<CompletableFuture<List<User>>> futures = new ArrayList<>();
        for (int i = 0; i < shardCount; i++) {
            final int shardIndex = i;
            futures.add(CompletableFuture.supplyAsync(() ->
                userMapper.selectFromShard(shardIndex, size * (page + 1))
            ));
        }
        // 2. 合并所有结果
        List<User> allUsers = futures.stream()
            .map(CompletableFuture::join)
            .flatMap(List::stream)
            .collect(Collectors.toList());
        // 3. 内存中排序并分页
        List<User> pageData = allUsers.stream()
            .sorted(Comparator.comparing(User::getId).reversed())
            .skip(page * size)
            .limit(size)
            .collect(Collectors.toList());
        return new PageResult<>(pageData, allUsers.size());
    }
}

六、 主流中间件与配置

常用中间件对比
中间件 类型 特点 适用场景
ShardingSphere 客户端 功能丰富,生态完善,Apache顶级项目 各种复杂分片场景
MyCat 服务端(代理) 配置简单,易于上手 中小型项目
Vitess 服务端(代理) YouTube开源,云原生(K8s)友好 大规模云原生环境
ShardingSphere 配置示例

以下是一个典型的水平分库分表配置,展示了如何使用数据库及中间件领域的热门工具ShardingSphere-JDBC。

# application-sharding.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://localhost:3306/ds0
        username: root
        password: root
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/ds1
        username: root
        password: root
    sharding:
      tables:
        users:
          actual-data-nodes: ds$->{0..1}.users_$->{0..3} # 指定物理数据节点
          table-strategy: # 分表策略
            inline:
              sharding-column: user_id
              algorithm-expression: users_$->{user_id % 4}
          database-strategy: # 分库策略
            inline:
              sharding-column: user_id
              algorithm-expression: ds$->{user_id % 2}

七、 最佳实践与原则

1. 分片键选择原则
  • 高基数:字段值分布均匀,如用户ID。
  • 业务相关性:是核心查询条件,避免非分片键查询带来的全路由扫描。
  • 避免热点:不会导致数据过度倾斜到某个分片。
  • 稳定性:字段值不经常更新,否则可能触发数据迁移。
2. 分片数量规划

合理评估数据增长趋势,预留扩容空间。

public class ShardingCalculator {
    public int calculateShardCount(long totalDataSize, int singleShardCapacity) {
        // 单分片建议容量(如:1000万-5000万行)
        int shardCount = (int) Math.ceil((double) totalDataSize / singleShardCapacity);
        // 为未来扩展预留Buffer(例如翻倍)
        return shardCount * 2;
    }
}
3. 平滑迁移方案(双写迁移)
@Service
public class DataMigrationService {
    public void migrateData() {
        // 1. 双写阶段:新旧库同时写入
        enableDoubleWrite();
        // 2. 历史迁移:将存量数据迁移至新分片
        migrateHistoricalData();
        // 3. 数据校验:确保新旧数据一致性
        validateDataConsistency();
        // 4. 流量切换:逐步将读流量切至新库
        switchReadTraffic();
        // 5. 下线老库:停止写入老库,完成迁移
        stopOldWrite();
    }
}

八、 面试要点精粹

一个结构化的回答思路:
“分库分表是解决数据库扩展性瓶颈的核心手段,主要分为垂直拆分和水平拆分。垂直拆分(分库、分表)侧重业务解耦,水平拆分(分库、分表)侧重解决单点数据量过大问题。

实践中需要重点考量分片键设计、全局ID生成、跨片查询聚合、分布式事务等挑战。解决方案包括选用Snowflake等分布式ID算法、应用层聚合数据、采用Seata处理事务等。

目前业界普遍采用ShardingSphere这类成熟中间件,它封装了SQL解析、路由、执行、结果归并等复杂逻辑,能极大降低开发复杂度。”

九、 何时应避免分库分表?

  • 数据量不大:单表数据远未达到千万级别。
  • 并发压力小:QPS在单机数据库轻松承受范围内。
  • 有更优解:可通过索引优化、读写分离、引入缓存(如Redis)等手段解决。
  • 团队能力不足:缺乏足够的运维和故障排查经验,复杂的分布式架构可能带来更大风险。



上一篇:铭凡MS-S1 MAX实测:低成本构建本地AI集群部署DeepSeek大模型
下一篇:MySQL索引底层原理与优势:为什么B+树是高频面试核心考点
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 17:29 , Processed in 0.149106 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表