先说几个关键点:悲观锁的 selectForUpdate() 确实会阻塞,但仅限于数据库行级锁,而且必须在事务内生效。脱离 DB::transaction(),或者没有关闭自动提交,它就可能静默失效,甚至退化成普通查询。乐观锁则完全不依赖数据库锁,靠 version 字段做并发校验,但需要自己拼 where 条件。至于 sharedLock(),它允许多个事务同时读,但会阻塞写操作——不过它防不住“丢失更新”这类问题。下面逐一拆解。

PHP怎么处理Eloquent Locking悲观与乐观锁_Lara vel并发控制【技巧】

悲观锁:selectForUpdate() 在事务中真的会阻塞吗

会,但前提是它确实在事务内运行,并且 where 条件命中了索引。否则,要么锁失效,要么直接锁表。一个经典错误是:SQLSTATE[HY000]: General error: 1205 Deadlock found when trying to get lock —— 多个请求同时对同一组记录加锁,但顺序不一致,触发 MySQL 死锁。

乐观锁:用 increment() 和 where version=xxx 避免覆盖写

乐观锁的核心是“先查后验”,不依赖数据库锁,而是靠业务字段(比如 versionupdated_at)做并发校验。Lara vel 自带的 increment()decrement() 是原子操作,但本身不带版本检查——你得手动拼 where 条件。

容易踩的坑:$model->version++ + $model->sa ve() 不是原子的,两个请求可能读到相同 version,都写成了 version=2,结果一次更新被覆盖。

selectForUpdate() 和 sharedLock() 的实际区别在哪

selectForUpdate() 加排他锁(X Lock),阻止其他事务读写该行;sharedLock() 加共享锁(S Lock),允许其他事务加 S 锁(即并发读),但会阻塞 X 锁(写操作)。两者都只在事务中有效,且都要求 where 条件命中索引。

一个典型误用:把 sharedLock() 当成“读不阻塞”的万能方案——但它并不能防止 A 读、B 写、A 再写导致的覆盖问题(即丢失更新),它只防“写-写冲突”,不防“读-写-写”。

高并发下 update() 带 where 条件为什么有时像乐观锁

因为 InnoDB 的 UPDATE 语句在执行时会先定位记录并加 X 锁,再判断 WHERE 条件是否满足;若不满足,锁会立刻释放。所以多个请求同时执行 User::where('balance', '>=', 100)->decrement('balance', 100),最终只有第一个成功扣减,其余因条件不满足而跳过——这看起来像乐观锁行为,但底层仍是悲观锁机制。

关键点在于:这不是“无锁重试”,而是“锁了再判,不满足就放”。如果业务需要明确知道是否被抢,不能只靠 affected rows 为 0 就认为失败,得结合具体逻辑判断(比如余额是否真不足)。

实际用哪一种,取决于你能否容忍“重试”、数据库是否支持行锁、以及业务对一致性的容忍边界。悲观锁容易死锁,乐观锁容易重试失败,而看似简单的 update() + where 其实暗藏锁生命周期细节——这些地方,线上出问题时往往最先崩。

本文转载于:https://www.php.cn/faq/2314023.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。