车位状态需用带条件的原子更新(如where id=123 and status='vacant')并配合redis分布式锁与乐观锁(version字段),计费逻辑统一收口应用层,时间统一存utc,超时预警用延迟消息而非定时扫描。

车位状态怎么安全更新,避免并发写错?
多个用户同时进出、管理员手动调整,status 字段极易被覆盖。直接 UPDATE parking_spaces SET status = 'occupied' WHERE id = 123 不行——没校验前置状态,可能把已占位的车位又设成“空闲”。
- 用带条件的原子更新:
UPDATE parking_spaces SET status = 'occupied', occupied_at = NOW() WHERE id = 123 AND status = 'vacant',返回影响行数为 0 就说明状态已变,需重试或报错 - 业务层加 Redis 分布式锁(键名如
lock:space:123),但锁粒度别太大——不要锁整个停车场,只锁单个车位 ID - 数据库表必须有
version字段或时间戳列,配合乐观锁;否则 MySQL 的READ COMMITTED隔离级别下仍可能读到过期状态
计费逻辑该在数据库里算,还是应用层算?
按分钟计费 + 免费时长 + 夜间折扣,这些规则一改就牵扯 SQL 硬编码,维护成本高。数据库函数(如 MySQL 的 stored procedure)看似省网络往返,实则难调试、难测试、难灰度发布。
- 计费核心逻辑统一收口到应用层函数,比如 Go 的
CalculateFee()或 Python 的calc_parking_fee(),输入是entry_time、exit_time、vehicle_type,输出是精确到分的费用 - 数据库只存原始时间字段:
entry_time、exit_time、fee_cents(整型,单位为分),不存中间计算过程 - 注意时区:所有时间统一转为 UTC 存储,显示时再转本地;否则跨城市部署时,
NOW()和用户提交时间对不上
SELECT ... FOR UPDATE 在什么场景下真有用?
不是所有查+改都要加行锁。SELECT ... FOR UPDATE 会阻塞其他事务的同记录读写,在高并发进出场景下容易成为性能瓶颈,甚至引发死锁。
- 仅在「先查状态再决定是否允许入场」这种强一致性要求下使用,例如:检查
status = 'vacant'后立刻更新为'occupied' - 必须确保 WHERE 条件命中索引(如
id或plate_number),否则会升级为表锁 - 锁范围要最小化:不要
SELECT * FROM parking_spaces FOR UPDATE,更不能在一个事务里先锁 A 车位、再锁 B 车位——顺序不一致就是死锁温床
免费时段和超时预警怎么不靠定时任务硬扫?
每天凌晨扫一遍全表找「已停满 24 小时未缴费」的车,数据量一上万就慢,还容易误伤正在缴费流程中的记录。
- 关键状态变更触发事件:车辆入场写入
entry_time,出场时更新exit_time和fee_cents,这两个动作天然就是边界点 - 超时预警用延迟消息(如 RabbitMQ TTL + DLX,或 Redis
ZADD+ZRANGEBYSCORE),入场时发一条 23h50m 后触发的提醒任务,比轮询精准且轻量 - 免费时段逻辑内聚在计费函数里,而不是靠字段
is_free标记——后者需要额外维护生命周期,而前者只要入口时间在规则窗口内,自然返回 0
真正难的不是写对一个车位的增删改查,而是当「同一辆车反复进出」「断网后补传数据」「管理员后台强制修改状态」这三件事同时发生时,occupied_at、exit_time、fee_cents 这三个字段还能互相印证、不留歧义。时间字段的精度、时区、来源,比业务规则本身更容易出问题。










