并发与恢复
一、两个用户同时购买同一商品会出现什么问题
1. 超卖问题(Overselling)
- 两个用户同时查询到商品状态为"未售出"
- 两个用户同时提交购买请求
- 系统可能为同一商品生成两个订单
- 导致一个商品被卖给两个买家
2. 数据不一致
- Item表中的status字段更新可能出现竞态条件
- Orders表中可能插入重复记录
- 违反"每个商品最多只能被交易一次"的业务规则
3. 丢失更新(Lost Update)
- 一个用户的更新操作可能被另一个用户的更新覆盖
- 导致数据状态不正确
- 例如:两个订单都成功,但商品状态只更新一次
二、如何解决(加锁/事务)
1. 数据库事务(Transaction)
- 使用BEGIN TRANSACTION开启事务
- 将查询商品状态、插入订单、更新商品状态作为一个原子操作
- 使用COMMIT提交或ROLLBACK回滚
- 确保ACID特性(原子性、一致性、隔离性、持久性)
2. 悲观锁(Pessimistic Locking)
- 使用SELECT FOR UPDATE对商品记录加排他锁
- 代码示例:
SELECT * FROM Item WHERE item_id = 'i001' FOR UPDATE; - 锁定期间其他事务无法修改该记录
- 适用于并发冲突频繁的场景
3. 乐观锁(Optimistic Locking)
- 添加版本号字段(version)或时间戳
- 更新时检查版本号是否变化
- 如果版本号变化,说明数据已被修改,操作失败需重试
- 适用于读多写少的场景
4. 唯一约束
- 在Orders表的item_id字段上设置UNIQUE约束
- 数据库层面防止同一商品被重复购买
- 尝试插入重复记录时会抛出异常
5. 应用层限流
- 使用分布式锁(如Redis的SETNX)
- 对同一商品的购买请求进行排队处理
- 确保同一时间只有一个请求在处理特定商品
三、如果系统崩溃,如何恢复订单数据
1. 数据库日志恢复
- 事务日志(Transaction Log/Redo Log):记录所有数据修改操作
- 系统崩溃后,通过重做日志恢复已提交但未写入磁盘的事务
- 通过撤销日志(Undo Log)回滚未提交的事务
- 确保数据的一致性和完整性
2. 检查点机制(Checkpoint)
- 定期将内存中的数据刷新到磁盘
- 记录检查点位置,减少恢复时间
- 恢复时只需处理检查点之后的日志
3. 数据备份策略
- 全量备份:定期备份整个数据库
- 增量备份:备份自上次备份以来的变化
- 差异备份:备份自上次全量备份以来的变化
- 制定备份计划:每日全量备份 + 每小时增量备份
4. 主从复制(Replication)
- 设置主数据库和从数据库
- 实时同步数据到从库
- 主库崩溃时可快速切换到从库
- 提供高可用性和数据冗余
5. 恢复流程
- ① 使用最近的备份文件恢复数据库
- ② 应用备份后的日志文件(Redo Log)
- ③ 回滚未完成的事务(Undo Log)
- ④ 验证数据一致性
- ⑤ 恢复服务运行