TP5中锁必须通过Db或模型的lock()方法配合事务使用,单独封装加锁函数无效;正确做法是在闭包事务内调用lock(true)->find()后立即更新并提交,且where条件须命中索引避免表锁。

TP5 里没有“自定义函数写锁”这种独立机制——锁是数据库操作层面的行为,必须依托 Db 或模型的 lock() 方法,配合事务使用。直接在 PHP 函数里写个 myLock() 是无效的,锁不住数据库行。
TP5 的 lock() 方法怎么调用才真正生效
它不是开关式函数,而是构造器链式方法,只影响紧随其后的 select()、find()、update() 等执行动作:
-
lock(true)生成FOR UPDATE(悲观锁),其他事务无法读写该行 -
lock('lock in share mode')生成共享锁,其他事务可读但不可写 - 必须和
startTrans()/transaction()配合,否则锁在语句执行完就释放 - 不能只查不更新:比如
->lock(true)->find()后没做任何 DML,锁意义不大,且可能被连接池复用导致意外残留
为什么单独封装一个“加锁函数”容易失效
常见错误是把锁逻辑抽成独立函数,比如:
function acquireOrderLock($orderNo) {
return Db::name('order')->where('order_no', $orderNo)->lock(true)->find();
}
问题在于:
- 这个函数返回后,事务未开启或已提交,锁早已释放
- 后续业务逻辑(如更新状态)不在同一事务内,根本没被锁保护
- PHP 层面的变量或静态属性无法传递数据库连接上下文,
lock()绑定的是当前查询对象的连接实例 - 若用模型调用,还必须确保模型配置了
auto_timestamp、type等不影响锁行为的选项
正确写法:锁、查、改、提交必须在同一个事务块内
推荐用闭包事务,避免手动 startTrans/commit/rollback 漏掉:
Db::transaction(function () use ($orderNo) {
$order = Db::name('order')
->where('order_no', $orderNo)
->lock(true)
->find();
if (!$order || $order['status'] != 1) {
throw new Exception('订单不可支付');
}
Db::name('order')->where('id', $order['id'])->update(['status' => 2]);
});
关键点:
-
lock(true)必须出现在find()前,且find()是触发 SQL 执行的动作 - 所有数据变更必须在同一事务内完成,不能拆到外面
- 不要依赖返回值判断锁是否“成功”——
find()返回数据只说明行存在且被锁住,不返回锁状态码 - 高并发下,建议在
where条件中加上状态过滤(如status = 1),减少锁竞争范围
真正容易被忽略的是:锁的有效性完全取决于 InnoDB 引擎是否实际命中索引。如果 where 条件没走索引,会升级为表锁,性能断崖下跌——务必用 EXPLAIN 确认执行计划。

















