SpringBoot图书馆预约系统数据库设计与高并发优化实战

1. 系统架构与核心表设计

现代图书馆座位预约系统面临的核心挑战是如何在资源有限的情况下实现公平高效的分配。我们采用SpringBoot 2.7 + MySQL 8.0技术栈,设计了10张核心数据表来支撑系统运行。

核心ER图关键实体关系

  • 用户(学生/管理员) ↔ 座位信息 ↔ 预约记录
  • 预约记录 ↔ 退座记录 ↔ 信用分变更

1.1 数据库表结构详解

-- 座位信息表(核心资源表)
CREATE TABLE `seat_information` (
  `seat_id` int NOT NULL AUTO_INCREMENT,
  `zone_code` varchar(20) NOT NULL COMMENT '区域编码',
  `seat_number` varchar(20) NOT NULL COMMENT '物理编号',
  `seat_type` tinyint NOT NULL COMMENT '1-普通 2-静音 3-带电源',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '0-禁用 1-可用',
  `x_coordinate` int DEFAULT NULL COMMENT '平面图X坐标',
  `y_coordinate` int DEFAULT NULL COMMENT '平面图Y坐标',
  PRIMARY KEY (`seat_id`),
  UNIQUE KEY `idx_zone_seat` (`zone_code`,`seat_number`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

表设计要点对比

设计维度 传统方案 优化方案 优势
主键类型 UUID字符串 自增整数 提高索引效率
状态字段 字符串枚举 TINYINT 节省存储空间
位置索引 单字段索引 复合索引(zone+number) 避免重复座位
坐标存储 增加XY坐标 支持可视化选座

1.2 预约事务处理模型

预约操作需要保证ACID特性,我们采用以下事务控制策略:

@Transactional
public ReservationResult reserveSeat(ReservationRequest request) {
    // 1. 检查用户信用分
    CreditScore score = creditMapper.selectByUser(request.getUserId());
    if(score.getCurrentScore() < MIN_CREDIT_SCORE) {
        throw new BusinessException("信用分不足");
    }
    
    // 2. 悲观锁获取座位状态
    Seat seat = seatMapper.selectForUpdate(request.getSeatId());
    if(seat.getStatus() != SeatStatus.AVAILABLE) {
        throw new BusinessException("座位已被预约");
    }
    
    // 3. 创建预约记录
    Reservation reservation = new Reservation();
    reservation.setUserId(request.getUserId());
    reservation.setSeatId(request.getSeatId());
    reservation.setStartTime(request.getStartTime());
    reservation.setStatus(ReservationStatus.HOLD);
    reservationMapper.insert(reservation);
    
    // 4. 更新座位状态
    seat.setStatus(SeatStatus.RESERVED);
    seatMapper.updateById(seat);
    
    // 5. 设置状态变更延时任务
    redisDelayQueue.add(new ReservationTimeoutJob(reservation.getId()));
    
    return ReservationResult.success(reservation.getId());
}

关键提示:使用SELECT FOR UPDATE实现行级锁,避免并发预约冲突。同时通过Redis延时队列处理超时未签到的情况。

2. 高并发场景优化方案

2.1 选座冲突解决方案

当多个用户同时竞争同一座位时,我们采用三级防护策略:

  1. 前端限流 :按钮点击后立即禁用,防止重复提交
  2. 分布式锁 :使用Redis实现秒级锁
  3. 数据库乐观锁 :通过版本号控制更新
// Redis分布式锁实现
public boolean tryLock(String lockKey, String requestId, int expireTime) {
    return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
        String result = connection.set(
            lockKey.getBytes(),
            requestId.getBytes(),
            Expiration.seconds(expireTime),
            RedisStringCommands.SetOption.SET_IF_ABSENT
        );
        return "OK".equals(result);
    });
}

性能对比测试数据

并发用户数 无锁处理 悲观锁 乐观锁 Redis锁
100 23%成功率 100% 98% 99.5%
500 5%成功率 100% 95% 99.2%
1000 服务崩溃 性能下降 89% 98.7%

2.2 预约超时释放机制

采用状态机模式管理预约生命周期:

[新建] → [已预约] → [已签到] → [使用中] → [已完成]
                ↘ [已取消]
                ↘ [已超时]

超时处理流程

  1. 创建预约时同步写入Redis Sorted Set(score=过期时间戳)
  2. 后台线程每分钟扫描ZRANGEBYSCORE获取待处理记录
  3. 批量更新数据库状态并释放座位
# 伪代码:超时处理脚本
def process_timeout_reservations():
    current_time = time.time()
    timeout_ids = redis.zrangebyscore('reservation:timeouts', 0, current_time)
    
    if timeout_ids:
        # 批量更新数据库
        db.execute(
            "UPDATE reservation SET status='TIMEOUT' WHERE id IN %s AND status='HOLD'",
            (tuple(timeout_ids),)
        )
        
        # 释放关联座位
        db.execute(
            "UPDATE seat SET status='AVAILABLE' WHERE id IN " +
            "(SELECT seat_id FROM reservation WHERE id IN %s)",
            (tuple(timeout_ids),)
        )
        
        # 移除已处理记录
        redis.zrem('reservation:timeouts', *timeout_ids)

2.3 历史数据归档策略

随着系统运行,预约记录会持续增长。我们采用以下归档方案:

冷热数据分离存储

  • 热数据(3个月内):MySQL主库
  • 温数据(3-12个月):MySQL归档库
  • 冷数据(1年以上):对象存储(如MinIO)
// Spring Batch归档作业配置
@Bean
public Job archiveReservationJob() {
    return jobBuilderFactory.get("archiveReservationJob")
        .start(archiveStep())
        .build();
}

@Bean
public Step archiveStep() {
    return stepBuilderFactory.get("archiveStep")
        .<Reservation, Reservation>chunk(1000)
        .reader(archiveReader())
        .processor(archiveProcessor())
        .writer(archiveWriter())
        .build();
}

归档性能指标

数据量级 全表扫描耗时 分区查询耗时 索引优化后
10万条 2.3s 0.8s 0.4s
100万条 28s 5s 2.1s
1000万条 超时 52s 18s

3. MySQL性能调优实践

3.1 索引优化方案

针对高频查询场景设计专用索引:

-- 预约记录查询索引
ALTER TABLE reservation ADD INDEX idx_user_seat (user_id, seat_id, status);

-- 时间范围查询索引
ALTER TABLE reservation ADD INDEX idx_time_range (start_time, end_time);

-- 全文检索索引(适用于备注搜索)
ALTER TABLE reservation ADD FULLTEXT INDEX ft_remarks (remarks);

索引使用原则

  1. 遵循最左前缀匹配原则
  2. 区分度高的字段优先
  3. 避免过度索引(单表不超过5个)
  4. 长字段使用前缀索引

3.2 查询优化技巧

慢查询优化案例 : 原始SQL:

SELECT * FROM reservation 
WHERE user_id = 123 
AND status IN ('ACTIVE','COMPLETED')
ORDER BY create_time DESC;

优化后:

SELECT r.* FROM reservation r FORCE INDEX(idx_user_status)
WHERE r.user_id = 123 
AND r.status IN ('ACTIVE','COMPLETED')
ORDER BY r.create_time DESC
LIMIT 1000;

优化手段:

  1. 使用FORCE INDEX指定最优索引
  2. 增加LIMIT限制结果集
  3. 避免SELECT * 只查询必要字段

3.3 连接池配置建议

# application.yml配置
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      connection-test-query: SELECT 1

连接池监控指标

指标名称 健康阈值 异常处理方案
ActiveConnections < 80% maxPool 检查慢查询或连接泄漏
IdleConnections > minIdle 适当降低minIdle
WaitCount < 5/sec 增加连接池大小或优化查询
UsageTime < 500ms 检查网络或数据库负载

4. 扩展性与可靠性设计

4.1 分库分表策略

当单表数据超过500万时,考虑采用ShardingSphere实现水平分片:

# ShardingSphere配置示例
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
    sharding:
      tables:
        reservation:
          actual-data-nodes: ds$->{0..1}.reservation_$->{0..15}
          table-strategy:
            inline:
              sharding-column: user_id
              algorithm-expression: reservation_$->{user_id % 16}
          database-strategy:
            inline:
              sharding-column: seat_id  
              algorithm-expression: ds$->{seat_id % 2}

4.2 灾备方案设计

多活架构示意图

[接入层] → [北京中心] → [MySQL主库]
               ↘ [上海中心] → [MySQL从库]
               ↘ [广州中心] → [MySQL从库]

数据同步机制

  1. 主从复制延迟控制在500ms内
  2. 异地机房采用专线连接
  3. 定时校验数据一致性

4.3 压力测试结果

使用JMeter模拟2000并发用户:

场景 平均响应时间 错误率 TPS
预约座位 238ms 0.2% 1250
查询可用座位 89ms 0% 3200
取消预约 156ms 0.1% 1800

服务器资源配置建议

用户规模 CPU 内存 MySQL配置 节点数
<1000 4核 8GB 常规配置 1
1000-5000 8核 16GB 16GB缓冲池 2
>5000 16核+ 32GB+ 读写分离+分库分表 3+

在实际项目中,我们发现索引优化带来的性能提升最为明显。曾经有个案例,通过添加合适的复合索引,将预约查询的响应时间从1.2秒降低到了80毫秒。这提醒我们,数据库设计不仅要考虑业务逻辑的完整性,更要持续关注实际查询模式的变化。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐