死锁报错需先检查事务是否过长、锁范围过大,如避免模糊查询、减少耗时操作、防止事务嵌套;thinkphp中可临时设read committed隔离级别,慎用全局配置;批量操作应合并sql,避免软删除导致全表扫描;通过innodb状态日志定位具体表与条件。

死锁报错 Deadlock found when trying to get lock 怎么快速定位和缓解
出现这个错误,说明两个或多个事务在互相等待对方释放锁,MySQL 已主动回滚其中一个。这不是代码写错了,而是并发写入路径重叠 + 事务执行顺序偶然导致的典型现象。
别急着改隔离级别或加重试——先确认是不是自己写的事务太长、锁范围太大:
-
SELECT ... FOR UPDATE或UPDATE语句是否查了没用的字段或用了模糊条件(如LIKE '%xxx'),导致锁住整张表或大量无关行 - 事务里是否混入了 HTTP 请求、文件读写、循环处理等耗时操作,让锁持有时间从毫秒拉长到秒级
- 是否在事务中调用了其他可能开启新事务的方法(比如 ThinkPHP 的
Db::transaction()嵌套,或模型事件里又触发了数据库操作)
ThinkPHP 中怎么安全设置事务隔离级别
MySQL 默认是 REPEATABLE READ,它在某些更新场景下比 READ COMMITTED 更容易引发间隙锁冲突,进而增加死锁概率。但不能全局改成 READ COMMITTED —— 它会影响一致性读行为,比如幻读变多、部分业务逻辑出错。
只在明确需要降低锁强度的写操作里临时调整:
- 用
Db::connect()->startTrans(['isolation' => 'read committed'])显式开启事务,注意该配置仅对当前连接生效 - 不要在
database.php配置里全局设'default_strict' => false或改'deploy' => 0来“绕过”隔离控制,这会让底层驱动忽略你传的参数 - ThinkPHP 6.3+ 支持
Db::transaction(function () { ... }, 'read committed'),但需确保 MySQL 版本 ≥5.7 且未启用innodb_locks_unsafe_for_binlog
Db::transaction() 里哪些写法会放大死锁风险
ThinkPHP 的事务封装本身不引入死锁,但常见误用会显著提升概率:
- 在事务内多次执行
Db::table('user')->where('status', 1)->update(...),每次都会重新扫描并加锁;应合并为单条批量更新,或先SELECT id FOR UPDATE锁定目标主键再逐条更新 - 用
saveAll()批量插入含唯一索引的数据,若存在重复值,MySQL 可能在不同索引上加锁顺序不一致,诱发死锁;建议先INSERT IGNORE或用ON DUPLICATE KEY UPDATE - 事务中调用
$model->withTrashed()->find()这类带软删除条件的查询,可能因隐式OR导致无法走索引,锁住更多行
死锁日志怎么看、怎么对应到 ThinkPHP 代码
MySQL 的 SHOW ENGINE INNODB STATUS\G 输出里,关键看 LATEST DETECTED DEADLOCK 段落:
- 找
TRANSACTION下的mysql tables in use和locked tables,确认是哪张表参与了冲突 - 看每个事务的
HELD LOCKS和WAITING FOR THIS LOCK TO BE GRANTED,比对锁类型(record lock/gap lock/next-key lock) - 把 SQL 中的表名、WHERE 条件和 ThinkPHP 里对应的
Db::name('order')->where(...)->update()行号对照,重点检查 WHERE 是否用了非索引字段、或者用了函数(如DATE(create_time))
真正麻烦的是那些锁发生在 JOIN 或子查询里的场景——ThinkPHP 的 with() 或闭包查询很容易无意中触发,这时候光看日志很难还原,得配合慢日志 + EXPLAIN 分析实际执行计划。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











