应采用数据库事务、redis原子计数器或预生成快照三种方案:事务通过select...for update加锁并校验库存与时间重叠;redis用lua脚本保证扣减与检测原子性;快照法将冲突检测转为内存区间比对。

在 ThinkPHP6.x 构建的预约系统中,若用户提交预约后出现同一时间段被重复占用、库存超售或冲突未拦截的情况,则说明时间段库存管理与预约冲突检测逻辑存在缺陷。以下是实现该逻辑的具体方法:
一、基于数据库事务的时间段库存扣减与冲突校验
该方法通过原子性操作确保库存扣减与预约写入同步完成,并在事务内前置校验当前时间段是否仍有可用库存且无时间重叠预约。避免因并发请求导致的超卖或冲突漏检。
1、开启数据库事务:使用 Db::transaction() 包裹后续操作。
2、执行 SELECT ... FOR UPDATE 查询目标时间段记录,锁定该行数据。
3、检查该时间段的 remaining_stock 字段是否大于 0。
4、执行子查询或 JOIN 检查是否存在已通过审核(status = 1)且 start_time 新预约 start_time 的预约记录。
5、若校验通过,则更新 remaining_stock = remaining_stock - 1,并插入新预约记录。
6、提交事务;若任一校验失败或异常,则回滚事务并返回错误提示:“该时间段已满或与其他预约冲突”。
二、使用 Redis 原子计数器实现高并发库存控制
该方法将每个可预约时间段映射为独立的 Redis key(如 appointment:2024-06-15:09-10),利用 INCR、DECR 和 EXISTS 等原子命令控制库存,并配合 Lua 脚本保证冲突检测与扣减的不可分割性。
1、构造 Redis key:格式为 appointment:{$date}:{$start}-{$end},例如 appointment:2024-06-15:09-10。
2、使用 Redis::eval 执行 Lua 脚本,脚本内先 GET 当前 key 值判断是否大于 0,再执行 DECR 并返回结果。
3、若 Lua 返回值 ≥ 0,表示扣减成功;否则视为库存不足。
4、在扣减成功后,异步写入 MySQL 预约主表,并记录关联的 Redis key 与业务 ID 映射关系。
5、定时任务或预约取消时,通过该映射关系调用 INCR 恢复库存,并清除过期 key:Redis key 必须设置 EXPIRE 时间,防止长期占用内存。
三、预生成时间段快照并离线校验冲突
该方法适用于预约频次高但时间段变更少的场景,预先为每日所有可选时间段生成唯一标识快照(含起止时间、最大容量、当前占用数),将冲突检测从实时 SQL 查询转为内存比对,显著降低数据库压力。
1、每日凌晨通过命令行任务生成当日快照:读取 time_slots 表,按 date + start_time + end_time 组合生成 MD5 标识,存入 cache_time_slot_snapshots 表。
2、用户提交预约前,先查缓存获取对应时间段快照的 current_occupied 数值。
3、遍历当日所有已存在的预约记录(WHERE date = ? AND status = 1),提取其时间段区间,与新预约区间做数学交集判断:交集长度 > 0 即判定为冲突。
4、若无冲突且 current_occupied
5、更新完成后,将新快照同步至 Redis 缓存,设置 30 分钟 TTL。
四、基于空间索引的时段重叠计算(MySQL 8.0+)
该方法利用 MySQL 8.0 引入的 GIS 功能,将每个预约时间段抽象为 LINESTRING(起点为 (start_timestamp, 0),终点为 (end_timestamp, 0)),借助 ST_Intersects 函数高效检测时间重叠,适用于大数据量下的精准冲突识别。
1、修改预约表结构,在 time_range 字段添加 LINESTRING 类型列,并建立 SPATIAL 索引。
2、插入预约时,将 start_time 和 end_time 转换为 UNIX_TIMESTAMP,构造 WKT 格式:LINESTRING(start_ts 0, end_ts 0)。
3、冲突检测语句使用:SELECT id FROM appointments WHERE date = ? AND status = 1 AND ST_Intersects(time_range, ST_GeomFromText('LINESTRING(?, 0, ?, 0)'))。
4、结合 EXPLAIN ANALYZE 验证查询是否命中 SPATIAL 索引:未命中索引时性能将急剧下降,必须确保字段类型与函数用法严格匹配。
5、应用层捕获查询结果集,若返回非空,则拒绝本次预约请求。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











