以下是电影票务系统的数据库设计和核心逻辑建议:
一、数据库表设计(MySQL示例)
- 电影表(movies)
CREATE TABLE movies (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL, -- 电影名称
duration INT, -- 片长(分钟)
genre VARCHAR(50), -- 影片类型
release_date DATE -- 上映日期
);
- 影院表(cinemas)
CREATE TABLE cinemas (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL, -- 影院名称
location VARCHAR(255) -- 地理位置
);
- 影厅表(halls)
CREATE TABLE halls (
id INT PRIMARY KEY AUTO_INCREMENT,
cinema_id INT NOT NULL, -- 所属影院
name VARCHAR(50), -- 影厅名称
seat_layout VARCHAR(20), -- 座位布局(如:10x20)
FOREIGN KEY (cinema_id) REFERENCES cinemas(id)
);
- 场次表(screenings)
CREATE TABLE screenings (
id INT PRIMARY KEY AUTO_INCREMENT,
movie_id INT NOT NULL, -- 电影ID
hall_id INT NOT NULL, -- 影厅ID
start_time DATETIME NOT NULL, -- 放映时间
price DECIMAL(8,2) NOT NULL, -- 票价
FOREIGN KEY (movie_id) REFERENCES movies(id),
FOREIGN KEY (hall_id) REFERENCES halls(id)
);
- 座位表(seats)
CREATE TABLE seats (
id INT PRIMARY KEY AUTO_INCREMENT,
hall_id INT NOT NULL, -- 所属影厅
row_num INT NOT NULL, -- 行号
col_num INT NOT NULL, -- 列号
seat_type ENUM('普通','VIP') -- 座位类型
FOREIGN KEY (hall_id) REFERENCES halls(id)
);
- 座位状态表(seat_status)
CREATE TABLE seat_status (
screening_id INT NOT NULL, -- 场次ID
seat_id INT NOT NULL, -- 座位ID
status ENUM('可选','已售','锁定'), -- 状态
lock_expire TIMESTAMP, -- 锁定过期时间
PRIMARY KEY (screening_id, seat_id),
FOREIGN KEY (screening_id) REFERENCES screenings(id),
FOREIGN KEY (seat_id) REFERENCES seats(id)
);
- 订单表(orders)
CREATE TABLE orders (
id VARCHAR(32) PRIMARY KEY, -- 订单号(建议用UUID)
user_id INT, -- 用户ID(如果有会员系统)
screening_id INT NOT NULL, -- 场次ID
total_amount DECIMAL(10,2), -- 总金额
status ENUM('待支付','已支付','已取消'),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (screening_id) REFERENCES screenings(id)
);
二、核心业务逻辑
- 余票查询逻辑
// 查询某场次可用座位数
public int getAvailableSeatsCount(int screeningId) {
String sql = "SELECT COUNT(*) FROM seat_status " +
"WHERE screening_id = ? AND status = '可选'";
// 执行查询并返回结果
}
// 获取详细座位状态
public List<SeatStatus> getSeatStatus(int screeningId) {
String sql = "SELECT seat_id, status FROM seat_status " +
"WHERE screening_id = ?";
// 返回座位状态列表
}
- 选座锁定逻辑(伪代码)
@Transactional
public boolean lockSeats(int screeningId, List<Integer> seatIds, int userId) {
// 1. 检查座位是否可用
String checkSql = "SELECT COUNT(*) FROM seat_status " +
"WHERE screening_id = ? AND seat_id IN (?) " +
"AND status != '可选'";
// 2. 设置临时锁定(15分钟有效期)
String updateSql = "UPDATE seat_status " +
"SET status = '锁定', lock_expire = NOW() + INTERVAL 15 MINUTE " +
"WHERE screening_id = ? AND seat_id IN (?)";
// 3. 记录锁定日志
// ...
}
- 支付成功处理
@Transactional
public void confirmPayment(String orderId) {
// 1. 更新订单状态为已支付
updateOrderStatus(orderId, "已支付");
// 2. 永久标记座位为已售
String sql = "UPDATE seat_status SET status = '已售' " +
"WHERE screening_id = ? AND seat_id IN " +
"(SELECT seat_id FROM order_seats WHERE order_id = ?)";
}
三、关键设计要点
- 并发控制
- 使用数据库事务 + 行级锁(SELECT FOR UPDATE)
- 考虑使用Redis分布式锁控制高并发请求
- 采用乐观锁机制处理库存变更
- 性能优化
- 对高频查询(如余票查询)使用Redis缓存
- 将座位状态表按场次分表(sharding)
- 使用消息队列异步处理订单状态变更
- 容错机制
- 定时任务释放过期锁定座位
- 订单支付超时自动回滚机制
- 数据库读写分离架构设计
- 数据一致性
- 通过事务保证座位状态与订单状态一致
- 使用补偿事务处理异常场景
- 定期对账校验库存与实际销售数据
建议根据实际业务规模调整设计方案,初期可先实现核心功能,后续逐步扩展会员系统、优惠券系统等模块。