MySQL 实战案例:电商系统商品库分表设计(按类目 时间分片)

在电商系统中,商品数据量大、访问频率高,如果不进行优化,单表的查询和写入性能会成为瓶颈。为了提升 MySQL 的性能,我们可以对商品库进行分库分表,常见的分片方式有 按类目分片按时间分片。本文将结合实际业务场景,探讨如何设计商品表的分片方案,并提供 SQL 及代码示例。


1. 为什么要进行分表?

在单库单表模式下,商品表通常设计如下:

CREATE TABLE product (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,  -- 自增主键
    name VARCHAR(255) NOT NULL,             -- 商品名称
    category_id INT NOT NULL,               -- 商品所属类目
    price DECIMAL(10,2) NOT NULL,           -- 商品价格
    stock INT NOT NULL,                     -- 商品库存
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

问题分析:

  • 数据量过大:电商平台有海量商品,表数据可能达到 千万级别甚至上亿,影响查询性能。
  • 索引性能下降:MySQL 索引树变大后,B+ 树的查询性能下降。
  • 单库并发瓶颈:高并发时,数据库负载过重,影响订单处理速度。

如何优化?

  1. 按类目(category_id)分片:适用于类目相对稳定的场景,提高查询效率。
  2. 按时间(created_at)分片:适用于新增商品量大、查询按时间范围为主的场景,如秒杀商品、活动商品。

2. 方案一:按类目分片

2.1 方案设计

核心思路:根据 category_id(商品分类 ID)进行水平拆分,将商品存储到多个表中。例如:

商品分类存储表
数码产品(category_id=1)product_1
服饰(category_id=2)product_2
家电(category_id=3)product_3

2.2 表结构设计

CREATE TABLE product_1 (  -- category_id=1
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    category_id INT NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE product_2 (  -- category_id=2
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    category_id INT NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2.3 分片规则

存储逻辑

  • category_id % N(N 为表的数量)
  • 例如,假设有 4 张表(product_0, product_1, product_2, product_3),则 category_id % 4 计算存储表。

SQL 查询示例(应用层路由)

public String getTableName(int categoryId) {
    int tableIndex = categoryId % 4; // 4 张表
    return "product_" + tableIndex;
}

// 查询商品
public Product queryProductById(int categoryId, long productId) {
    String tableName = getTableName(categoryId);
    String sql = "SELECT * FROM " + tableName + " WHERE id = ?";
    return jdbcTemplate.queryForObject(sql, new Object[]{productId}, new ProductRowMapper());
}

2.4 适用场景

适用于

  • 类目较稳定,查询通常基于 category_id 过滤的场景。
  • 商品分类较多,每个类目数据量较均衡。

不适用于

  • 类目分布不均衡(部分分类数据量过大,造成单表热点)。
  • 查询跨多个分类(如搜索功能),需要跨表查询,可能影响性能。

3. 方案二:按时间分片

3.1 方案设计

核心思路:按照 created_at(商品发布时间)进行分片,如每月或每季度存储到不同的表,适用于新增商品量大、查询多按时间范围进行的场景。

例如:

时间范围存储表
2024 年 1 月product_202401
2024 年 2 月product_202402
2024 年 3 月product_202403

3.2 表结构设计

CREATE TABLE product_202401 (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    category_id INT NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE product_202402 (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    category_id INT NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3.3 分片规则

存储逻辑

  • created_atYYYYMM 生成表名,例如 product_202401 表示 2024 年 1 月的商品表。

SQL 查询示例(应用层路由)

public String getTableNameByTime(LocalDateTime createdAt) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM");
    String tableSuffix = createdAt.format(formatter);
    return "product_" + tableSuffix;
}

// 查询最近 3 个月商品
public List<Product> queryRecentProducts() {
    String tableName1 = getTableNameByTime(LocalDateTime.now().minusMonths(1));
    String tableName2 = getTableNameByTime(LocalDateTime.now().minusMonths(2));
    String sql = "SELECT * FROM " + tableName1 + " UNION ALL SELECT * FROM " + tableName2;
    return jdbcTemplate.query(sql, new ProductRowMapper());
}

3.4 适用场景

适用于

  • 时间范围查询多,如“查询最近 3 个月商品”或“查询 2023 年的商品”。
  • 新商品数据量大,但历史数据查询少,可归档历史表优化性能。

不适用于

  • 查询跨多个时间段时,需要跨表查询,SQL 复杂度增加。
  • 历史数据查询频繁的场景,可能影响查询效率。

4. 方案对比与选型建议

方案适用场景优势劣势
按类目分片类目查询多查询高效,索引小类目分布不均会导致数据倾斜
按时间分片新增商品量大,按时间查询多便于归档,减少索引压力跨时间查询需要 UNION ALL

如何选择?

  • 如果查询主要按类目筛选,优先按类目分片
  • 如果新商品数据量大,查询按时间范围筛选,优先按时间分片
  • 可以结合两者先按类目分库,再按时间分表,兼顾查询效率和可扩展性。

5. 总结

  • 按类目分片适合分类查询多的场景,但可能有数据倾斜问题。
  • 按时间分片适合新增数据量大、按时间查询多的场景,但历史数据查询需跨表。
  • 合理设计分库分表策略,可提升 MySQL 性能,支撑电商系统高并发、高可扩展性。

📌 有什么问题和经验想分享?欢迎在评论区交流、点赞、收藏、关注! 🎯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫比乌斯之梦

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值