高并发下thinkphp必须事务与行级写锁配合使用,用select for update在事务中锁定记录确保查改原子性,lock(true)须紧接where后且where条件必须命中索引,按场景选悲观锁或乐观锁,并控制事务粒度、禁用持久连接。

高并发下 ThinkPHP 的锁与事务必须配合使用,单靠事务不加锁会读到过期库存,只加锁不控事务则锁无效。核心是用行级写锁(SELECT FOR UPDATE)在事务中锁定具体记录,确保查+改原子性。
行锁必须嵌在事务里才生效
lock(true) 不是独立操作,它依赖事务上下文生成真正有效的 FOR UPDATE 语句。脱离事务调用,MySQL 会当作自动提交语句执行,锁瞬间释放,起不到串行保护作用。
- 务必先调用 Db::startTrans() 或用 Db::transaction() 包裹逻辑
- lock(true) 要紧接在 where() 之后、find() 之前,例如:
$goods = Db::name('goods')->where('id', $id)->lock(true)->find(); - 更新操作无需再加 lock(true),行锁由上一步 find 持有,直到 commit 或 rollback 才释放
避免锁表:WHERE 条件必须命中索引
如果 where 字段没有主键或唯一索引,InnoDB 可能升级为间隙锁甚至全表锁,导致整个商品表被堵死,吞吐量断崖下跌。
- 确认 id 字段已建主键或唯一索引(这是硬性前提)
- 用 EXPLAIN 验证 SQL 是否走索引:
EXPLAIN SELECT * FROM goods WHERE id = 123 FOR UPDATE;
type 必须是 const 或 eq_ref,否则立即补索引 - 禁止用非索引字段(如 name、status)做 lock 查询条件
按场景选悲观锁 or 乐观锁
悲观锁适合冲突频繁、一致性要求极高的场景(如秒杀扣库存);乐观锁适合冲突概率低、可接受重试的业务(如文章阅读数+1)。
- 悲观锁:用 lock(true) + 事务,绝对安全但并发低,易阻塞
- 乐观锁:表加 version 字段,查询时带 version,更新时 where 同时校验 version,失败则重试
- 兜底不能少:update 语句仍需二次校验,例如 where('stock', '>=', $num),防锁失效或网络重试导致超卖
事务粒度要小,连接要干净
长事务拖住锁、占住连接,是死锁和性能瓶颈的常见源头。
- 事务内只做必要数据库操作,避免调用外部 API、发邮件、写日志等耗时行为
- database.php 中将 'persistent' => false,禁用持久连接,防止连接复用导致锁残留
- 复杂流程考虑降级:比如把强一致的订单生成拆成“预占+异步落库”,用消息队列削峰
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











