Laravel - 锁等待超时

22

我的代码中有许多交易,在其中一项交易执行时发生错误,但不会触发提交或回滚,则数据库会被锁定,并且任何后续尝试访问数据库的操作都会导致以下结果:

production.ERROR: PDOException: SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction in /home/forge/default/vendor/laravel/framework/src/Illuminate/Database/Connection.php:390

在控制器中:

DB::beginTransaction();

try {
    //Code that uses exec() to process some images. <-- If code breaks here, then the above error appears on subsequent requests.
    //Code that accesses the database
}
catch(\Exception $e){
    DB::rollback();
    throw $e;
}
DB::commit();

所以即使使用 php artisan migrate:refresh 或者 php artisan migrate:reset 命令也无法正常工作。我该如何解决这个问题?

7个回答

13

以下是我从经验中总结的一些提示...

如果您正在进行测试驱动开发,请分离出哪些测试组合会导致错误。利用生态系统提供的任何机制来选择性地运行测试(例如,在测试方法上使用@group onlyphpunit --group only)。

其次,缩短锁定等待超时时间(SET GLOBAL innodb_lock_wait_timeout = 10)。这样可以快速获得反馈,不必花费整天等待测试运行。具体效果因情况而异,请根据您的特定条件进行调整。

第三,寻找未关闭的事务,例如没有回滚或提交的begin操作。这正是我的问题所在。我的try/catch没有将足够的逻辑封装起来,在begin transaction和try-catch-rollback之间出现了错误。

第四,考虑将事务的所有部分放置在同一个try-catch中,这有助于确保所有部分都在场且易于查看。例如:

    try {
        DB::beginTransaction();
        $this->someMethodThatMightThrow();
        DB::commit();
    } catch (Exception $e) {
        DB::rollBack();
        throw $e;
    }

那是我的个人看法。希望对互联网上的某些人有所帮助。


12
在运行phpunit时,我遇到了这个错误,但无法弄清原因。我在@Stoutie这里找到的答案对我很有帮助,最终发现一个被覆盖的tearDown()方法,在那里我没有调用parent::tearDown()。基本上,如果您不调用parent::tearDown(),则事务仍然打开,并且回滚/断开连接从未发生过。 - alexkb
我在我的tearDown方法中也错过了parent :: tearDown(),并看到了相同的等待锁超时问题。 - AndraeRay
哇,我也错过了对 parent::tearDown() 的调用,这让我调试了24个小时。感谢@Stoutie!! - bilogic
我在遇到这个问题之前一直没有意识到try-catch块中有一个拼写错误,我不小心将\Exceptio 错误地输入成了 \Exception - undefined

9

我看到了重复的问题

如何在MySQL上调试锁等待超时?

您应该考虑通过设置innodb_lock_wait_timeout来增加InnoDB的锁等待超时值,默认值为50秒。

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.01 sec)

您可以在 /etc/my.cnf 文件中添加以下一行,将其设置为更高的值,以使其永久生效:
[mysqld]
innodb_lock_wait_timeout=120

并重新启动 mysql。如果此时无法重新启动 mysql,请运行以下命令:

SET GLOBAL innodb_lock_wait_timeout = 120; 

您可以只在会话期间设置它。
SET innodb_lock_wait_timeout = 120; 

1
我认为我的问题在于锁定没有过期。在我描述问题后出现错误,我等待了五分钟,但它仍然被锁定。 - Snowball
是的,我也遇到了这个问题,而且在我的情况下,导致的锁定似乎是无限期的,直到手动干预。 - kjones
1
不确定这个能否帮到你,因为你没有使用队列,但是当我使用数据库驱动处理队列时,我遇到了和你一样的错误。我使用了php artisan queue:work --daemon命令,并且只有一个工作进程。后来切换到queue:listen命令解决了这个问题。 - kjones

1
从XAMPP重启Apache和MySQL对我解决了问题。

1

重新启动Apache和MySQL解决了问题。

对于Linux系统:

sudo service mysql restart
sudo service apache2 restart

0

这个问题与mysql数据库有关。我曾经遇到过同样的问题,通过以下步骤成功解决了它。

找到usr/local/var/mysql/your_computer_name.local.err文件,并了解更多关于错误的信息。

位置:/usr/local/var/mysql/your_computer_name.local.err

这可能是权限问题。

  1. 查找mysql是否正在运行并杀死它

ps -ef | grep mysql

kill -9 PID

其中PID是第二列的值 2. 检查mysql的所有权

ls -laF /usr/local/var/mysql/

if it is owned by root, change it mysql or your user name



sudo chown -R mysql /usr/local/var/mysql/

0

重启数据库解决了我的问题。


3
重启只会终止持有锁的查询,但不能解决问题。运行“show processlist”命令并杀死持有所需锁的事务相应的ID比重启更好。 - Benson Okello

0

很有可能你的异常没有被捕获,因为它不是异常而是致命错误或其他类型的 \Throwable

您可以使用带有回调的DB::transaction方法,这是一种更安全的替代方法,因为它为您处理了所有内容,并且几乎等同于您的代码(除了它也捕获\Throwable)。

DB::transaction(function () {
    $this->someMethodThatMightThrow();
});

或者确保你用这个捕获一切

DB::beginTransaction();
try {
  //..
  DB::commit();
} catch (\Throwable $e) {
  DB::rollback();
  throw $e;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接