🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
✨那些你一个人走过的夜路,终将化作照亮未 来的光



做业务后端一段时间后会发现:CRUD 谁都会写,真正体现设计的是「规则怎么落库、并发怎么防、状态怎么收敛」。本文用一个影院票务类项目的思路,把几块常见业务逻辑拆开讲------偏概念,方便对照自己的代码理解。
一、后端到底在护什么
票务核心卖的不是「电影」这个抽象概念,而是:
某场次 + 某些座位 + 一笔订单
所以后端要保证的大致是:
- 同一座位不能卖给两个人(防超卖)
- 选座后未支付不能永久占着(要超时释放)
- 订单和座位状态要一致(不能有单没座、有座没单)
- 能认出「当前请求是谁」(登录态)
下面按模块说。
二、登录态:Cookie + Session
HTTP 是无状态的,每次请求默认互不相认。常见做法是:
- 浏览器:Cookie 里存一个 Session ID(随机串)
- 服务器:用这个 ID 查到「当前用户是谁」
Session 内容可以放在:
| 存储 | 特点 |
|---|---|
| 内存 map | 实现简单;进程重启丢失;多机难共享 |
| 数据库 | 可持久 |
| Redis | 线上常见,快且好过期 |
为什么不把完整用户信息直接塞进 Cookie?
- 浏览器侧用户可篡改、也可能被 XSS 偷到
- 服务器侧保管用户信息更可控;客户端只持有一张「号码牌」
补充:
- Cookie 常设
HttpOnly,降低 JS 读取风险 - 同一浏览器配置下 Cookie 共享,所以多个窗口一般不能同时登录两个账号;无痕窗口或不同浏览器才是两套 Cookie
一句话:浏览器持 ID,服务器持用户信息。
三、下单:事务 + 行锁 + 业务锁
选座下单时,通常要同时做两件事:
- 创建待支付订单
- 把座位改成「锁定」
1. 为什么要事务(Transaction)
这两步必须原子完成:要么都成功,要么都失败。
否则可能出现:
- 有订单,座位没锁上
- 座位锁了,订单没建成功
事务里任何一步出错就回滚,避免脏数据。
2. 为什么还要行锁(SELECT ... FOR UPDATE)
防止并发下两人同时读到「座位空闲」并都下单成功。
典型过程:
- A 对座位行加
FOR UPDATE,检查空闲,创建订单并锁定 - B 同时来,在行锁上等待
- A 提交后释放行锁
- B 再读,发现已锁定/已售,业务失败返回
行锁解决的是「同一瞬间的并发写冲突」。
3. 业务锁字段是干什么的
行锁在事务结束后就释放了。座位被谁占用、占用到几点,要靠业务字段长期表达,例如:
status:空闲 / 锁定 / 已售lock_user_id:谁锁的lock_until:锁到什么时候- 订单上的
pay_deadline:支付截止时间
可以记成:
行锁防并发;业务字段表达占用规则与超时。
四、「15 分钟」到底怎么实现
产品说「锁定 15 分钟未支付自动取消」,实现上通常是:
- 下单时 :
截止时间 = 当前时间 + 15 分钟,写入lock_until/pay_deadline - 后台定时任务 :每隔一段时间(如 30 秒)扫描
- 待支付且已过
pay_deadline→ 订单超时取消,座位释放 - 座位仍锁定且已过
lock_until→ 释放座位
- 待支付且已过
注意区分两个配置:
| 配置 | 含义 |
|---|---|
| 15 分钟 | 业务有效期(锁多久、要在多久内支付) |
| 30 秒 | 清理任务多久跑一次 |
清理函数往往不再接收 15,因为截止时间已经落库;它只比较「现在」和「库里的绝对时间」。
这也解释了:把配置从 15 改成 5,一般只影响之后新下的单,已经写下的截止时间不会自动变。
五、座位状态流转
常见三态:
空闲 ──下单──▶ 锁定 ──支付──▶ 已售
▲ │
└────取消/超时─┘