You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP使用InnoDB行锁时innodb_lock_wait_timeout配置不生效问题

问题原因

本质是两个实现错误导致现象不符合预期:

  1. lockName字段没加唯一索引
    没有唯一索引的前提下,WHERE lockName="mylock"的条件没法走索引精准定位行,只能全表扫描,InnoDB会给扫过的所有记录、记录间的间隙全加上排他Next-Key锁,锁范围完全不可控。而且因为没有唯一键约束,INSERT IGNORE不会触发冲突忽略,两个脚本会各插入一条mylock的重复记录,进一步把锁逻辑搞乱。
  2. innodb_lock_wait_timeout配置没在事务里生效
    部分版本的PDO MySQL驱动中,事务开启前设置的会话级事务参数,会在beginTransaction()触发事务初始化时被重置为全局默认值(默认是50秒)。你配置的1秒超时根本没作用到后续的加锁请求上,第二个脚本实际用的是默认50秒的超时时间,等30秒第一个事务提交就拿到锁了,自然不会抛超时异常。

另外先确认下表引擎是不是InnoDB,要是用了MyISAM这类不支持行锁的引擎,FOR UPDATE直接加全表锁,行锁超时配置完全不生效。

解决方案

按下面步骤调整就行:

  1. 先给lockName加唯一索引,保证行锁能精准落到单条记录上,同时避免重复插入锁记录:
ALTER TABLE locksPoc ADD UNIQUE INDEX idx_lock_name (lockName);
  1. 调整代码顺序,把超时配置移到事务开启之后,同时精简查询逻辑缩小锁范围:
<?php
$conn = new \PDO('mysql:host=localhost;dbname=locks', 'root', 'root', [
    \PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    \PDO::ATTR_AUTOCOMMIT => true
]);
// 初始化锁记录,加了唯一索引后这里只会保留一条mylock记录
$conn->exec('INSERT IGNORE INTO locksPoc (lockName) VALUES ("mylock")');
$this->dump('Lock exists in DB');
$conn->beginTransaction();
// 事务启动后再设置锁等待超时,避免被事务初始化逻辑重置
$conn->exec('set session innodb_lock_wait_timeout = 1');
// 调试阶段可以打开下面的注释,确认超时配置真的生效
// $currentTimeout = $conn->query('SELECT @@innodb_lock_wait_timeout')->fetchColumn();
// if ((int)$currentTimeout !== 1) throw new \Exception("锁超时配置未生效");
// 只查需要的索引字段,不要用SELECT *,进一步缩小锁影响范围
// 要排他锁用FOR UPDATE,要共享锁换成FOR SHARE(MySQL8.0+)/LOCK IN SHARE MODE(兼容旧版本)即可
$rows = $conn->query('SELECT lockName FROM locksPoc WHERE lockName="mylock" FOR UPDATE');
$row = $rows->fetchAll();
$this->dump('Got the lock');
sleep(30);
$conn->commit();

调整后再跑并行测试,第二个脚本等待1秒就会抛出锁等待超时异常,符合预期。

内容的提问来源于stack exchange,提问作者Tomáš Fejfar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 00:06:24