核心是“检查-修改”非原子操作引发竞争,需用InnoDB行级悲观锁(SELECT FOR UPDATE)、精准索引、短事务、版本号重试、幂等校验等组合策略保障一致性。

ThinkPHP数据库事务并发冲突,核心在于“检查-修改”非原子操作引发的竞争,尤其在库存扣减、订单生成、计数更新等场景。解决不是靠回避事务,而是让事务真正起作用、锁得准、范围小、兜底稳。
用行级悲观锁锁定关键数据
别用LOCK TABLES或无条件查询,直接在事务内对目标记录加SELECT FOR UPDATE:
- 调用
Db::name('product')->where('id', $id)->lock(true)->find(),必须在Db::startTrans()之后 - 确保表引擎是InnoDB,MyISAM不支持行锁,锁会静默失效
- WHERE条件必须命中索引(主键或唯一索引),否则可能升级为表锁
- 锁只在事务提交或回滚后释放,避免在事务里做HTTP请求、日志写入等耗时操作
按场景选对锁策略
不是所有地方都要上悲观锁,低冲突业务可用更轻量方式:
- 高一致性要求(如秒杀库存):用
lock(true)+ 短事务,配合Db::raw("stock - 1")原子更新 - 低冲突+可重试(如用户资料更新):加
version字段,更新时校验WHERE id = ? AND version = ?,失败则重试 - 仅需防重复插入(如日志、埋点):用
insertAll($data, ['ignore' => true]),但注意它不更新已有数据
事务本身要精简可控
事务越长,锁持有越久,死锁和排队风险越高:
立即学习“PHP免费学习笔记(深入)”;
- 只包裹
select + update/insert/delete这几步,剥离模型事件、缓存写入、外部API调用 - 优先用
Db::transaction(function () { ... })闭包方式,框架自动捕获异常并回滚,减少手动遗漏 - 批量操作分块处理,例如每50条一个事务,避免单事务锁住几百行导致阻塞
- 确认
app_debug = false且sql_debug = false,调试日志会拖慢事务执行速度
加一层应用层兜底
数据库锁解决不了全部问题,需结合业务逻辑加固:
- 前端加按钮防重复点击,后端接口做幂等性校验(如传入唯一
request_id,Redis记录已处理) - 超卖类场景,数据库扣减后加二次校验:
if ($newStock - 高频读写分离场景,写操作走主库+锁,读操作走从库+缓存,避免主从延迟引发误判
- 把部分强一致性操作下沉到队列异步执行,比如下单成功后发消息扣库存,接口只保证下单成功



















