数据库分库分表(单表5000万数据如何破局?这才是分库分表的正确打开方式)

数据库分库分表(单表5000万数据如何破局?这才是分库分表的正确打开方式)
单表5000万数据如何破局?这才是分库分表的正确打开方式

当你的单表数据突破5000万,查询从毫秒变成秒级,是时候考虑分库分表了。

一个真实案例:订单表5000万的噩梦

去年我们接手一个电商项目,订单表数据量达到5200万。一个简单的SELECT * FROM orders WHERE user_id = 12345 LIMIT 10,从原来的30ms飙升到3.2秒。索引优化、SQL调优都试过了,收效甚微。

这就是单表数据量过大带来的性能衰减魔咒——B+Tree索引层级加深,IO次数增加,内存放不下热点数据。

破局点:分库分表

分库分表不是银弹,但确实是解决大数据量下数据库性能问题的核心手段

第一步:明确分表策略

我们选择了水平分表,按user_id取模:

-- 原表结构CREATE TABLE `orders` (  `id` bigint NOT NULL AUTO_INCREMENT,  `user_id` int NOT NULL,  `order_no` varchar(64) NOT NULL,  `amount` decimal(10,2) DEFAULT NULL,  `create_time` datetime DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_user_id` (`user_id`)) ENGINE=InnoDB;-- 分表后:拆成16张表 orders_0 ~ orders_15-- 分片规则:user_id % 16

第二步:分库分表落地

分库数量:4个库,每个库4张表 = 16张表

// 分片算法public String getTableName(Long userId) {    int dbIndex = userId % 4;      // 库路由    int tableIndex = userId % 16;   // 表路由    return "db_" + dbIndex + ".orders_" + tableIndex;}

建表脚本

-- 在4个库中分别执行CREATE TABLE `orders_0` LIKE `orders_template`;CREATE TABLE `orders_1` LIKE `orders_template`;-- ... 直到 orders_15

第三步:使用ShardingSphere-JDBC

引入依赖:

    org.apache.shardingsphere    shardingsphere-jdbc-core-spring-boot-starter    5.3.2

配置分片规则:

spring:  shardingsphere:    datasource:      names: ds0,ds1,ds2,ds3      ds0:        url: jdbc:mysql://localhost:3306/db_0        username: root        password: 123456      # ds1,ds2,ds3 类似配置    rules:      sharding:        tables:          orders:            actual-data-nodes: ds$->{0..3}.orders_$->{0..15}            table-strategy:              standard:                sharding-column: user_id                sharding-algorithm-name: user-mod        sharding-algorithms:          user-mod:            type: MOD            props:              sharding-count: 16

第四步:改造业务代码

插入数据

@Servicepublic class OrderService {        @Autowired    private OrderMapper orderMapper;        public void createOrder(Order order) {        // ShardingSphere自动根据user_id路由到对应分片        orderMapper.insert(order);    }        public Order getOrderByUser(Long userId, Long orderId) {        // 查询条件必须包含分片键 user_id        return orderMapper.selectByUserIdAndOrderId(userId, orderId);    }}

跨分片查询

// 不包含分片键的查询,会扫描所有分片public List getOrdersByAmount(BigDecimal amount) {    // 这种查询会广播到16张表,性能较差    return orderMapper.selectByAmount(amount);}// 解决方案:建立冗余表或使用ES

第五步:数据迁移方案

使用停机迁移(凌晨2-4点):

-- 1. 创建分表-- 2. 暂停业务写入-- 3. 迁移数据INSERT INTO db_0.orders_0 SELECT * FROM old_db.orders WHERE user_id % 16 = 0;INSERT INTO db_0.orders_1 SELECT * FROM old_db.orders WHERE user_id % 16 = 1;-- ... 共16条INSERT-- 4. 切换数据源配置-- 5. 恢复业务

如果要求不停机,用binlog同步 + 双写方案。

效果对比

指标

优化前(单表5200万)

优化后(16张分表)

单条查询(带分片键)

3.2秒

25ms

插入操作

120ms

18ms

统计查询(count)

8.7秒

0.6秒

数据库分库分表(单表5000万数据如何破局?这才是分库分表的正确打开方式)

索引大小

2.8GB

每表180MB

关键避坑指南

1. 分片键必须慎选

选错分片键,80%的查询都会变成全表扫描。我们选user_id是因为90%的业务查询都带用户维度。

2. 全局ID生成

不能用自增ID,改用雪花算法:

public class SnowflakeIdWorker {    private static final SnowflakeIdWorker INSTANCE = new SnowflakeIdWorker(1, 1);        public static long nextId() {        return INSTANCE.nextId();    }}

3. 避免跨分片JOIN

-- 禁止这种查询SELECT o.*, u.name FROM orders_0 o JOIN users u ON o.user_id = u.id;

改为冗余存储多次查询聚合

4. 扩容方案

一致性哈希替代取模,扩容时只需迁移1/n的数据:

// 一致性哈希路由public String getTableNameConsistentHash(Long userId) {    int hash = Hashing.consistentHash(userId, 32); // 32个虚拟节点    return "orders_" + (hash % 16);}

什么时候该分库分表?

不是所有表都要分。我们的判断标准:

  • 单表数据 > 2000万,准备分表方案
  • 单表数据 > 5000万,必须分表
  • 单表数据 > 1亿,分库分表联合使用

小表示例(配置表、字典表)保持单表即可。

总结

分库分表的核心三要素:

  1. 分片键——决定90%的查询性能
  2. 分片数量——2的幂次方,方便扩容
  3. 路由算法——取模 or 一致性哈希

记住一句话:分库分表不是技术炫耀,是业务增长到一定阶段的必然选择。 提前规划,从容应对。

你的单表数据到多少了?欢迎在评论区交流。

文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有

最新文章

热门文章

本栏目文章