高性能订单表设计的艺术 🎨

59 阅读15分钟

一、开篇故事:小卖部vs大型超市 🏪

小卖部(简单设计)

订单本:
  订单号 | 客户 | 商品 | 数量 | 金额 | 时间
  
特点:
  → 一页纸记录所有信息
  → 查询方便
  → 但数据量大后翻页慢
  → 一个商品占一行(多商品怎么办?)

大型超市(复杂设计)

订单主表:
  订单号 | 客户 | 总金额 | 时间 | 状态

订单明细表:
  明细号 | 订单号 | 商品 | 数量 | 单价

客户表:
  客户ID | 姓名 | 电话 | 地址

商品表:
  商品ID | 名称 | 价格 | 库存
  
特点:
  → 数据分离,职责清晰
  → 避免冗余
  → 查询需要JOIN
  → 但数据量大时更高效

这就是订单表设计的权衡:范式化 vs 反范式化


二、订单表设计的核心挑战 🎯

2.1 业务特点

1. 数据量大
   → 日订单量:百万级
   → 年订单量:数亿级

2. 读多写少
   → 下单:写入
   → 查询订单、统计:大量读取

3. 数据不可变
   → 订单一旦生成,很少修改
   → 只能取消,不能删除

4. 查询场景多
   → 用户查自己的订单
   → 商家查订单
   → 客服查订单
   → 财务统计
   → 数据分析

5. 实时性要求高
   → 下单后立即可查
   → 状态变化实时更新

2.2 设计目标

✅ 性能:
   - 写入快(下单不能慢)
   - 查询快(各种查询场景)

✅ 可扩展性:
   - 数据量增长时性能不下降
   - 支持分库分表

✅ 数据一致性:
   - 订单数据准确
   - 库存扣减不能错

✅ 易维护:
   - 表结构清晰
   - 业务逻辑简单

三、订单表设计方案 📋

3.1 基础方案:主从表结构

表结构

-- 订单主表(orders)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    order_no VARCHAR(50) NOT NULL COMMENT '订单号',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
    pay_amount DECIMAL(10,2) NOT NULL COMMENT '实付金额',
    discount_amount DECIMAL(10,2) DEFAULT 0 COMMENT '优惠金额',
    status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态:0待付款 1已付款 2已发货 3已完成 4已取消',
    pay_type TINYINT COMMENT '支付方式:1微信 2支付宝 3银行卡',
    pay_time DATETIME COMMENT '支付时间',
    delivery_time DATETIME COMMENT '发货时间',
    finish_time DATETIME COMMENT '完成时间',
    cancel_time DATETIME COMMENT '取消时间',
    remark VARCHAR(500) COMMENT '备注',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    
    UNIQUE KEY uk_order_no (order_no),
    KEY idx_user_id_time (user_id, create_time),
    KEY idx_status_time (status, create_time),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';

-- 订单明细表(order_items)
CREATE TABLE order_items (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    order_id BIGINT NOT NULL COMMENT '订单ID',
    order_no VARCHAR(50) NOT NULL COMMENT '订单号(冗余)',
    product_id BIGINT NOT NULL COMMENT '商品ID',
    product_name VARCHAR(200) NOT NULL COMMENT '商品名称(冗余)',
    product_image VARCHAR(500) COMMENT '商品图片(冗余)',
    product_price DECIMAL(10,2) NOT NULL COMMENT '商品单价',
    quantity INT NOT NULL COMMENT '购买数量',
    total_price DECIMAL(10,2) NOT NULL COMMENT '小计金额',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    
    KEY idx_order_id (order_id),
    KEY idx_order_no (order_no),
    KEY idx_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';

-- 订单扩展表(order_ext)(可选)
CREATE TABLE order_ext (
    order_id BIGINT PRIMARY KEY COMMENT '订单ID',
    receiver_name VARCHAR(50) COMMENT '收货人',
    receiver_phone VARCHAR(20) COMMENT '收货电话',
    receiver_province VARCHAR(50) COMMENT '省',
    receiver_city VARCHAR(50) COMMENT '市',
    receiver_district VARCHAR(50) COMMENT '区',
    receiver_address VARCHAR(500) COMMENT '详细地址',
    invoice_type TINYINT COMMENT '发票类型',
    invoice_title VARCHAR(200) COMMENT '发票抬头',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单扩展信息表';

设计亮点

✅ 主从表分离
   - orders: 核心信息,查询频繁
   - order_items: 明细信息,支持多商品
   - order_ext: 扩展信息,不常查询

✅ 数据冗余
   - order_items中冗余product_name、product_image
   - 原因:商品信息可能变化,订单要保留下单时的信息

✅ 索引设计
   - uk_order_no: 订单号唯一索引(快速查询)
   - idx_user_id_time: 用户查自己订单(最左匹配)
   - idx_status_time: 按状态查询(运营、客服使用)
   - idx_create_time: 按时间范围查询(统计分析)

✅ 字段设计
   - 使用BIGINT作为主键(支持大数据量)
   - 金额使用DECIMAL(避免精度丢失)
   - 时间字段分离(pay_time、delivery_time等,便于统计)
   - create_time和update_time自动更新

3.2 优化方案:冗余设计(反范式化)⭐⭐⭐⭐⭐

问题

-- 查询用户订单,需要显示商品信息
SELECT 
    o.order_no, 
    o.total_amount, 
    o.create_time,
    p.product_name,  -- 需要JOIN products表
    p.product_image
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.user_id = 12345;

-- 问题:
-- → 需要JOIN 3张表
-- → 如果分库分表,可能跨库JOIN
-- → 性能差

解决方案:数据冗余

-- 在订单表中冗余常用字段
CREATE TABLE orders (
    -- ... 基础字段 ...
    
    -- 冗余用户信息(便于商家查询)
    user_name VARCHAR(50) COMMENT '用户名(冗余)',
    user_phone VARCHAR(20) COMMENT '用户电话(冗余)',
    
    -- 冗余收货信息(便于查询)
    receiver_name VARCHAR(50) COMMENT '收货人(冗余)',
    receiver_phone VARCHAR(20) COMMENT '收货电话(冗余)',
    receiver_address VARCHAR(500) COMMENT '收货地址(冗余)',
    
    -- 冗余商品信息(单品订单)
    product_id BIGINT COMMENT '商品ID(单品订单)',
    product_name VARCHAR(200) COMMENT '商品名称(单品订单)',
    product_image VARCHAR(500) COMMENT '商品图片(单品订单)',
    
    -- 冗余商家信息(便于商家查询自己的订单)
    shop_id BIGINT COMMENT '店铺ID',
    shop_name VARCHAR(100) COMMENT '店铺名称(冗余)',
    
    -- ...其他字段...
);

-- 查询时不需要JOIN
SELECT 
    order_no, 
    total_amount, 
    create_time,
    product_name,
    product_image,
    receiver_name,
    receiver_address
FROM orders
WHERE user_id = 12345;
-- ✅ 单表查询,性能好!

冗余的取舍

✅ 应该冗余的字段:
   - 下单时的快照数据(商品名称、价格、图片)
   - 查询频繁的字段(用户名、收货地址)
   - 关联表的关键字段(shop_name)

❌ 不应该冗余的字段:
   - 会频繁变化的字段(商品库存)
   - 很少查询的字段
   - 大字段(商品详情等)

3.3 分库分表设计 ⭐⭐⭐⭐⭐

分表策略

-- 按时间分表(推荐)
orders_202401
orders_202402
orders_202403
...

-- 或者按用户ID分库分表
-- 库:db0, db1, db2, db3(按user_id % 4)
-- 表:每个库下orders_0, orders_1, ... orders_255(按user_id % 256)

-- ShardingSphere配置
spring:
  shardingsphere:
    rules:
      sharding:
        tables:
          orders:
            actual-data-nodes: db${0..3}.orders_${0..255}
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: db-mod
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: table-mod
        
        sharding-algorithms:
          db-mod:
            type: MOD
            props:
              sharding-count: 4
          
          table-mod:
            type: MOD
            props:
              sharding-count: 256

分表后的查询

-- ✅ 带user_id查询(可以路由到具体分片)
SELECT * FROM orders WHERE user_id = 12345 AND status = 1;
-- → 路由到 db1.orders_123

-- ❌ 不带user_id查询(需要扫描所有分片)
SELECT * FROM orders WHERE order_no = 'O202401150001';
-- → 需要查询所有1024个分片(4库×256表)

-- 解决方案1:订单号中包含user_id
-- O{userId}_{timestamp}_{random}
-- O12345_1705298400_001

-- 解决方案2:单独的订单号路由表
CREATE TABLE order_route (
    order_no VARCHAR(50) PRIMARY KEY,
    user_id BIGINT NOT NULL,
    shard_key INT NOT NULL COMMENT '分片键',
    KEY idx_user_id (user_id)
) ENGINE=InnoDB;

-- 先查路由表获取user_id,再查订单表

3.4 读写分离 ⭐⭐⭐⭐

架构:
  主库(Master): 处理写入(下单、更新状态)
  从库(Slave1): 处理查询(用户查订单)
  从库(Slave2): 处理统计(数据分析)

好处:
  → 读写分离,减轻主库压力
  → 从库可以针对查询优化(不同索引)
// Spring配置
@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource masterDataSource() {
        // 主库配置
    }
    
    @Bean
    public DataSource slave1DataSource() {
        // 从库1配置
    }
    
    @Bean
    public DataSource slave2DataSource() {
        // 从库2配置
    }
    
    @Bean
    public DataSource routingDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        targetDataSources.put("slave1", slave1DataSource());
        targetDataSources.put("slave2", slave2DataSource());
        
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(masterDataSource());
        return dataSource;
    }
}

// 使用
@Service
public class OrderService {
    
    @Transactional
    @DataSource("master")
    public void createOrder(Order order) {
        // 写入主库
        orderMapper.insert(order);
    }
    
    @DataSource("slave1")
    public List<Order> getUserOrders(Long userId) {
        // 从从库查询
        return orderMapper.selectByUserId(userId);
    }
}

四、索引设计详解 📊

4.1 核心索引

-- 1. 订单号唯一索引(最重要)
UNIQUE KEY uk_order_no (order_no)
-- 使用场景:
--   - 按订单号查询(最频繁)
--   - 防止重复下单

-- 2. 用户+时间联合索引(用户查询)
KEY idx_user_id_time (user_id, create_time)
-- 使用场景:
--   - 用户查自己的订单
--   - 按时间倒序排列
--   - 支持分页

-- 3. 状态+时间联合索引(运营查询)
KEY idx_status_time (status, create_time)
-- 使用场景:
--   - 查询待发货订单
--   - 查询待付款订单(超时取消)

-- 4. 时间索引(统计分析)
KEY idx_create_time (create_time)
-- 使用场景:
--   - 统计某时间段订单量
--   - 数据归档

4.2 可选索引

-- 5. 店铺+时间索引(商家查询)
KEY idx_shop_id_time (shop_id, create_time)
-- 使用场景:
--   - 商家查看自己的订单

-- 6. 用户+状态+时间索引(覆盖索引)
KEY idx_user_status_time (user_id, status, create_time, order_no, total_amount)
-- 使用场景:
--   - 用户查询某状态的订单
--   - 覆盖索引,不需要回表

-- 注意:索引不是越多越好
--   - 每个索引占用空间
--   - 每次写入都要更新索引
--   - 维护成本高

4.3 索引使用案例

-- 案例1:用户查询自己的订单
SELECT order_no, total_amount, status, create_time
FROM orders
WHERE user_id = 12345
ORDER BY create_time DESC
LIMIT 10;

-- 使用索引:idx_user_id_time
-- 分析:
--   1. 通过idx_user_id_time快速定位user_id=12345的范围
--   2. 索引已经按create_time排序,直接返回前10条
--   3. 需要回表获取order_no、total_amount、status

-- 优化(覆盖索引):
CREATE INDEX idx_user_time_cover (user_id, create_time, order_no, total_amount, status);
-- 现在不需要回表,性能提升 ✅

-- 案例2:查询待支付订单(超时取消)
SELECT order_no, user_id, create_time
FROM orders
WHERE status = 0
  AND create_time < DATE_SUB(NOW(), INTERVAL 30 MINUTE);

-- 使用索引:idx_status_time
-- 分析:
--   1. 通过idx_status_time快速定位status=0的范围
--   2. 在status=0的范围内,create_time是有序的
--   3. 范围查询create_time < xxx

-- 案例3:按订单号查询
SELECT * FROM orders WHERE order_no = 'O202401150001';

-- 使用索引:uk_order_no(唯一索引)
-- 分析:
--   1. 直接通过订单号定位,最快
--   2. 时间复杂度:O(log n)

五、性能优化技巧 🚀

5.1 订单号设计

方案1:雪花算法(推荐)
  → 分布式唯一ID
  → 趋势递增(有序,对B+树友好)
  → 包含时间戳(可反推时间)
  
示例:1705298400123456789
  ↑ 时间戳 | 机器ID | 序列号

方案2:自定义规则
  → {日期}{用户ID后4位}{时间戳后6位}{随机3位}
  → 20240115{1234}{298400}{001}
  → 可读性好,包含业务信息
  
示例:O20240115123429840001
  ↑ 日期 | 用户ID | 时间戳 | 序列

优点:
  ✅ 订单号中包含分片键(user_id)
  ✅ 可以直接路由到具体分片
  ✅ 减少跨库查询

5.2 状态机设计

// 订单状态枚举
public enum OrderStatus {
    WAIT_PAY(0, "待付款"),
    PAID(1, "已付款"),
    DELIVERED(2, "已发货"),
    FINISHED(3, "已完成"),
    CANCELLED(4, "已取消"),
    REFUND(5, "退款中"),
    REFUNDED(6, "已退款");
    
    private final int code;
    private final String desc;
}

// 状态机
public class OrderStateMachine {
    
    // 允许的状态转换
    private static final Map<OrderStatus, Set<OrderStatus>> ALLOWED_TRANSITIONS = 
        Map.of(
            WAIT_PAY, Set.of(PAID, CANCELLED),
            PAID, Set.of(DELIVERED, REFUND, CANCELLED),
            DELIVERED, Set.of(FINISHED, REFUND),
            FINISHED, Set.of(REFUND),
            REFUND, Set.of(REFUNDED, FINISHED)
        );
    
    public boolean canTransition(OrderStatus from, OrderStatus to) {
        return ALLOWED_TRANSITIONS.getOrDefault(from, Set.of()).contains(to);
    }
    
    public void updateStatus(Order order, OrderStatus newStatus) {
        if (!canTransition(order.getStatus(), newStatus)) {
            throw new BusinessException("非法的状态转换");
        }
        
        order.setStatus(newStatus);
        
        // 记录状态变更时间
        switch (newStatus) {
            case PAID:
                order.setPayTime(new Date());
                break;
            case DELIVERED:
                order.setDeliveryTime(new Date());
                break;
            case FINISHED:
                order.setFinishTime(new Date());
                break;
            case CANCELLED:
                order.setCancelTime(new Date());
                break;
        }
        
        orderMapper.updateById(order);
    }
}

5.3 缓存优化

@Service
public class OrderService {
    
    @Autowired
    private RedisTemplate<String, Order> redisTemplate;
    
    @Autowired
    private OrderMapper orderMapper;
    
    // 查询订单(带缓存)
    public Order getOrderByNo(String orderNo) {
        String cacheKey = "order:" + orderNo;
        
        // 1. 先查Redis
        Order order = redisTemplate.opsForValue().get(cacheKey);
        
        if (order != null) {
            return order;  // 缓存命中
        }
        
        // 2. 查数据库
        order = orderMapper.selectByOrderNo(orderNo);
        
        if (order != null) {
            // 3. 写入缓存(30分钟过期)
            redisTemplate.opsForValue().set(
                cacheKey, 
                order, 
                30, 
                TimeUnit.MINUTES
            );
        }
        
        return order;
    }
    
    // 更新订单(删除缓存)
    @Transactional
    public void updateOrder(Order order) {
        // 1. 更新数据库
        orderMapper.updateById(order);
        
        // 2. 删除缓存(Cache Aside模式)
        String cacheKey = "order:" + order.getOrderNo();
        redisTemplate.delete(cacheKey);
    }
}

5.4 分页优化

// 问题:深分页很慢
SELECT * FROM orders 
WHERE user_id = 12345 
ORDER BY create_time DESC 
LIMIT 10000, 10;
-- 需要扫描10010行,只返回10行,浪费!

// 解决方案1:记录lastId(推荐)
SELECT * FROM orders 
WHERE user_id = 12345 
  AND id < #{lastId}  -- 上次查询的最后一条ID
ORDER BY id DESC 
LIMIT 10;
-- 只扫描10行,性能好 ✅

// 解决方案2:二次查询(先查ID)
// 第一步:查ID(索引覆盖,快)
SELECT id FROM orders 
WHERE user_id = 12345 
ORDER BY create_time DESC 
LIMIT 10000, 10;
-- 假设返回ID:[100, 99, 98, ...]

// 第二步:根据ID查完整数据
SELECT * FROM orders WHERE id IN (100, 99, 98, ...);

5.5 批量插入优化

// ❌ 不好的做法(一条一条插入)
for (OrderItem item : items) {
    orderItemMapper.insert(item);
}
// 假设100个商品,需要100次数据库交互

// ✅ 好的做法(批量插入)
orderItemMapper.batchInsert(items);
// 只需要1次数据库交互

// MyBatis实现
<insert id="batchInsert" parameterType="list">
    INSERT INTO order_items 
    (order_id, order_no, product_id, product_name, quantity, total_price)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.orderId}, #{item.orderNo}, #{item.productId}, 
         #{item.productName}, #{item.quantity}, #{item.totalPrice})
    </foreach>
</insert>

六、数据归档策略 📦

问题:
  → 订单表数据量越来越大
  → 查询变慢
  → 备份耗时

解决方案:数据归档

策略:
  1. 热数据(近3个月):保留在主表
  2. 温数据(3-12个月):归档到历史表
  3. 冷数据(1年以上):归档到OSS或数据仓库
-- 主表(热数据)
CREATE TABLE orders (
    -- ... 字段同上 ...
) ENGINE=InnoDB;

-- 历史表(温数据)
CREATE TABLE orders_history (
    -- ... 字段同上 ...
) ENGINE=InnoDB;

-- 归档脚本(定时任务执行)
-- 1. 将3个月前的订单移到历史表
INSERT INTO orders_history
SELECT * FROM orders 
WHERE create_time < DATE_SUB(NOW(), INTERVAL 3 MONTH);

-- 2. 删除主表中的数据
DELETE FROM orders 
WHERE create_time < DATE_SUB(NOW(), INTERVAL 3 MONTH)
LIMIT 10000;  -- 分批删除,避免锁表

-- 3. 冷数据导出到OSS
SELECT * FROM orders_history
WHERE create_time < DATE_SUB(NOW(), INTERVAL 12 MONTH)
INTO OUTFILE '/data/orders_cold_2023.csv';

-- 4. 删除历史表中的冷数据
DELETE FROM orders_history
WHERE create_time < DATE_SUB(NOW(), INTERVAL 12 MONTH)
LIMIT 10000;

七、实战案例:大促订单表设计 💼

场景

双11大促:
  - 日订单量:1000万
  - 峰值QPS:10万/秒
  - 用户查询订单:高并发

解决方案

-- 1. 分库分表(1024个分片)
--    4个库 × 256个表

-- 2. 订单主表(极简设计,只保留核心字段)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(50) NOT NULL,
    user_id BIGINT NOT NULL,
    shop_id BIGINT NOT NULL,
    total_amount DECIMAL(10,2) NOT NULL,
    pay_amount DECIMAL(10,2) NOT NULL,
    status TINYINT NOT NULL,
    pay_type TINYINT,
    create_time DATETIME NOT NULL,
    pay_time DATETIME,
    
    -- 冗余常用字段(减少JOIN)
    user_name VARCHAR(50),
    shop_name VARCHAR(100),
    product_name VARCHAR(200),  -- 主商品名称
    product_image VARCHAR(500),  -- 主商品图片
    
    UNIQUE KEY uk_order_no (order_no),
    KEY idx_user_time (user_id, create_time),
    KEY idx_shop_time (shop_id, create_time),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;  -- 压缩存储

-- 3. 订单扩展表(低频字段)
CREATE TABLE order_ext (
    order_id BIGINT PRIMARY KEY,
    receiver_info JSON,  -- 收货信息(JSON存储)
    extra_info JSON,     -- 扩展信息(JSON存储)
    create_time DATETIME
) ENGINE=InnoDB;

-- 4. 订单明细表(独立分片)
CREATE TABLE order_items (
    id BIGINT PRIMARY KEY,
    order_id BIGINT NOT NULL,
    order_no VARCHAR(50) NOT NULL,
    product_id BIGINT NOT NULL,
    product_name VARCHAR(200),
    quantity INT,
    total_price DECIMAL(10,2),
    
    KEY idx_order_id (order_id),
    KEY idx_order_no (order_no)
) ENGINE=InnoDB;

缓存策略

// 多级缓存
public Order getOrder(String orderNo) {
    // L1: 本地缓存(Caffeine,1分钟)
    Order order = localCache.get(orderNo);
    if (order != null) {
        return order;
    }
    
    // L2: Redis缓存(30分钟)
    order = redisCache.get(orderNo);
    if (order != null) {
        localCache.put(orderNo, order);
        return order;
    }
    
    // L3: 数据库
    order = orderMapper.selectByOrderNo(orderNo);
    if (order != null) {
        redisCache.set(orderNo, order, 30, TimeUnit.MINUTES);
        localCache.put(orderNo, order);
    }
    
    return order;
}

八、面试高频问题 🎤

Q1: 订单表如何设计?

答:

  1. 主从表分离:orders主表 + order_items明细表 + order_ext扩展表
  2. 数据冗余:冗余商品名称、价格(订单快照)、收货地址等
  3. 索引设计:uk_order_no、idx_user_id_time、idx_status_time
  4. 分库分表:按user_id哈希分片,或按时间分表
  5. 读写分离:主库写、从库读

Q2: 为什么要冗余商品信息?

答: 因为商品信息可能会变化(价格调整、名称修改),但订单要保留下单时的快照。如果不冗余,查询订单时JOIN商品表,可能显示当前价格而非下单时价格,数据不准确。

Q3: 订单号如何设计?

答: 推荐雪花算法或自定义规则:

  • 雪花算法:分布式唯一、趋势递增
  • 自定义:{日期}{用户ID}{时间戳}{序列号},如O20240115123429840001
  • 好处:包含时间信息、包含分片键、可读性好

Q4: 如何优化订单查询性能?

答:

  1. 索引优化:建立覆盖索引,避免回表
  2. 分库分表:减少单表数据量
  3. 缓存:Redis缓存热点订单
  4. 读写分离:查询走从库
  5. 数据归档:定期归档历史订单
  6. 分页优化:使用lastId代替offset

Q5: 订单表数据量过大如何处理?

答:

  1. 分库分表:水平拆分,按user_id或时间
  2. 数据归档:将历史数据归档到历史表或OSS
  3. 冷热分离:热数据MySQL,冷数据HBase/OSS
  4. 表压缩:使用ROW_FORMAT=COMPRESSED
  5. 索引优化:删除不必要的索引

九、总结口诀 📝

订单表设计学问多,
主从分离职责清。
数据冗余有必要,
快照信息要保存。

索引设计要合理,
用户查询最频繁。
订单号、用户ID,
状态时间也重要。

分库分表少不了,
按用户ID来哈希。
读写分离提性能,
主库写来从库读。

缓存优化效果好,
Redis本地两级缓。
数据归档定期做,
冷热分离是关键!

参考资料 📚


下期预告: 151-MySQL的online DDL原理和注意事项 🔧


编写时间:2025年
作者:技术文档小助手 ✍️
版本:v1.0

愿你的订单表设计如艺术般完美! 🎨✨