thinkphp事务中锁表或死锁本质是innodb行锁/表锁、事务执行顺序与引擎配置共同导致的问题;需先确认是否myisam引擎、是否存在未提交长事务、是否误用lock(true)于非更新场景。

ThinkPHP 事务中锁表或死锁,不是框架问题,而是 InnoDB 行锁/表锁 + 事务顺序 + 引擎配置共同作用的结果。直接改代码前,先确认是不是 MyISAM 引擎、有没有没 commit 的长事务、是否误用了 lock(true) 在非更新场景。
为什么 lock(true) 有时卡住请求,有时又没反应
因为 lock(true) 在 ThinkPHP 中实际生成的是 SELECT ... FOR UPDATE(InnoDB 行锁),但前提是:
- 数据表引擎必须是
InnoDB—— MyISAM 不支持行级锁,lock(true)会静默失效或退化为表级阻塞 - 必须在事务内调用,且后续有写操作(
update/save等),否则锁可能被提前释放或不生效 - WHERE 条件必须命中索引,否则会升级为表锁(比如
WHERE status=0无索引时) - 多个请求同时对同一行加
FOR UPDATE,后到的会等待;对不同行则互不影响
常见错觉:“加了 lock(true) 没用”,其实是查的那行没被其他事务锁住,或者根本没进事务上下文。
如何快速定位当前 MySQL 死锁或锁等待
别猜,直接查 information_schema 视图。在 MySQL 命令行或 phpMyAdmin 执行:
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started DESC LIMIT 5;
看 trx_state 是 LOCK WAIT 还是 RUNNING,再结合 trx_started 判断是否卡太久。接着查:
SELECT * FROM information_schema.innodb_lock_waits;
能直接看到哪个事务在等哪个事务的哪把锁。如果返回空,说明没有活跃锁等待,可能是应用层逻辑卡住(比如 sleep(10) 写在事务里),不是数据库锁的问题。
注意:SHOW FULL PROCESSLIST 只能看到连接状态,看不到锁关系,优先用上面两个视图。
ThinkPHP 中锁表(LOCK TABLES)和行锁(lock(true))怎么选
绝大多数业务场景该用行锁,而不是显式锁表。理由很实在:
-
LOCK TABLES interface WRITE是全局表锁,整个表只能被当前连接读/写,其他所有请求全排队 —— 并发一上来就雪崩 -
lock(true)是 InnoDB 自动管理的行锁,只锁 WHERE 匹配的记录,性能和扩展性好得多 - 锁表必须手动
UNLOCK TABLES,一旦异常没执行,表就一直被锁;而事务里的行锁在commit或rollback后自动释放 - 只有极少数离线批量导入、报表统计等低频场景才考虑锁表,且应避开业务高峰
如果你发现团队里有人在订单接口里写 $db->execute('LOCK TABLES orders WRITE'),请立刻拦住 —— 这不是防并发,这是造瓶颈。
事务提交失败导致锁残留的典型表现和修复
最常踩的坑:事务里抛异常但没 rollback(),或者 commit() 后又继续操作数据库,导致连接没释放、事务没结束。现象是:
- 同一行反复查询变慢,甚至超时
-
innodb_trx里能看到trx_state = RUNNING且trx_started是几小时前的 - 新请求执行
SELECT ... FOR UPDATE直接卡住,日志报SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded
临时解法(仅限紧急恢复):
KILL [thread_id]; -- 从 innodb_trx.trx_mysql_thread_id 获取
根治办法:ThinkPHP 事务必须用 try/catch 包裹,且 catch 中明确调用 rollback();更推荐用闭包式事务 Db::transaction(function () { ... });,它会自动处理回滚。
真正难排查的,往往不是锁本身,而是那个忘了 commit 的 sleep(30) 调试代码,还提交到了生产环境。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











