先查mysql错误日志路径:show variables like 'log_error';再检查该文件末尾含"* (1) transaction:"的死锁块;未开启全量记录则执行set global innodb_print_all_deadlocks = on;接着用show engine innodb status\g定位latest detected deadlock区域,分析事务id、持有锁、等待锁及sql语句。

phpEnv 是开发环境套件,它本身不引发死锁;死锁只发生在 MySQL 的 InnoDB 引擎执行事务时,排查必须进数据库看日志、查状态,不能在 phpEnv 界面或配置里“点点就解决”。
怎么看 phpEnv 里的 MySQL 死锁日志
phpEnv 默认用的是本地 MySQL 实例,错误日志路径通常在 phpEnv\mysql\data\*.err(Windows)或 /usr/local/phpenv/mysql/data/*.err(macOS/Linux),但更可靠的方式是登录 MySQL 后查真实路径:
SHOW VARIABLES LIKE 'log_error';
确认路径后,检查该文件末尾是否有 *** (1) TRANSACTION: 开头的块——这就是死锁现场。如果没开全量记录,需先执行:
SET GLOBAL innodb_print_all_deadlocks = ON;
注意:这个设置重启 MySQL 后会失效,要写入 my.cnf 的 [mysqld] 段才持久生效。
用 SHOW ENGINE INNODB STATUS 定位冲突 SQL
在 phpEnv 自带的 phpMyAdmin 或命令行中执行:
SHOW ENGINE INNODB STATUS\G
重点找 LATEST DETECTED DEADLOCK 区域,里面会显示:
- 两个事务各自的
TRANSACTIONID 和开始时间 - 各自
HELD LOCKS(持有哪些行锁/间隙锁) - 各自
WAITING FOR THIS LOCK TO BE GRANTED(等什么锁) - 触发死锁的完整
SQL语句(含参数值)
拿到这些 SQL 后,回到 PHP 代码里搜索对应 DAO 方法、Eloquent 查询或原生 mysqli 调用,就能快速锁定是哪段业务逻辑在并发下乱序操作了。
PDO 捕获死锁并重试的最小可行写法
phpEnv 下默认 PDO 驱动不会自动抛异常,必须显式开启错误模式:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
事务内捕获死锁码 1213,重试逻辑要干净:
- 每次重试前必须调
$pdo->beginTransaction(),不能复用已中断的事务 - 不要用
sleep(1),改用usleep(50000 * (1 做指数退避 - 重试上限设为 3 次,超过说明 SQL 顺序或索引设计有问题,不是靠重试能掩盖的
示例片段:
for ($i = 0; $i beginTransaction();
$pdo->prepare("UPDATE orders SET status = ? WHERE id = ?")->execute(['shipped', $id]);
$pdo->commit();
break;
} catch (PDOException $e) {
if ($e->getCode() === '1213') {
$pdo->rollback();
usleep(50000 * (1 <h3>为什么 phpEnv 项目更容易出现死锁</h3><p>不是 phpEnv 有 bug,而是它的典型使用场景放大了死锁风险:</p>
- 本地开发常开多个浏览器标签反复刷接口,等价于高并发压测,但没做请求节流
- 表结构常缺联合索引,
WHERE seller_id = ? AND state = 'NEW'这类条件若没索引,UPDATE 会升级成间隙锁,锁住更大范围 - 事务里混着
file_get_contents、curl_exec等阻塞操作,导致锁持有时间被拉长数秒 - 测试数据少,ID 分布集中,多条 UPDATE 碰巧落在同一索引页,物理争抢加剧
真正要改的不是 phpEnv 配置,而是把本地事务逻辑对齐线上标准:删掉事务里的 sleep/curl、补全 WHERE 字段的索引、所有跨表更新按约定顺序执行——比如永远先 users,再 orders,最后 logs。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











