【问题标题】:How to avoid jobs DB table locks issue when using Laravel queues?使用 Laravel 队列时如何避免作业数据库表锁定问题?
【发布时间】:2015-12-05 04:54:18
【问题描述】:

我正在使用 Laravel 5.1。

队列用于在多个系统之间获取/同步数据。

我使用数据库驱动,3个“artisan queue:work --daemon”进程一直在运行。

作业由系统用户和调度程序 (cron) 分派。三个队列用于确定作业的优先级。

一切似乎都运行良好 - 作业表中充满了记录,系统会处理它们并删除已完成的记录。

但是,一段时间后,锁定问题开始出现影响:

SQLSTATE[40001]: 序列化失败: 1213 Deadlock found when 试图获得锁;尝试重启事务

'RuntimeException' 带有消息'Can't swap PDO instance while inside 交易。'

SQLSTATE[HY000]:一般错误:1205 超过锁定等待超时;尝试 重启交易

我还没有尝试使用其他队列驱动程序。不过,我真的很想继续使用数据库。引擎是 InnoDB,jobs 表有默认结构和索引。

有没有办法解决这个问题?你有什么想法?

值得一提的是,我在作业类中调用了DB::reconnect(),因为队列工作人员作为守护进程运行。

正如人们所期望的那样,使用DispatchesJobs trait 调度作业。我不会以任何其他方式干扰队列算法。

【问题讨论】:

  • 有什么解决办法吗?
  • 不。暂时移到beantalkd。虽然我更喜欢 DB 驱动程序,因为我可以通过简单地查看 DB 表来跟踪作业和失败的作业......
  • 研究 Laravel 如何处理 MySQL 的“事务”。听起来您已经开始交易,然后坐了很长时间。 COMMIT 尽快交易。检查autocommit的使用情况。
  • 正确索引和使用 MySQL 事务可以帮助您配对、回溯与锁定表相关的每个模块,以便您可以查明哪个函数或部分代码“生成”了锁定。干杯
  • 不,为那个项目使用 beanstalkd。虽然我更喜欢数据库队列

标签: mysql laravel laravel-5 queue innodb


【解决方案1】:

我正在 Laravel 上编写队列管理系统,我有多个工作,我应该向多个用户发送电子邮件。我正在与主管一起运行许多工作人员,并避免向同一用户发送多封电子邮件,我编写了此代码。希望它能帮助解决这个问题的人

DB::transaction(function () use ($job) {

            if (!count($job->jobUsers()->sharedLock()->get())) { // to share reading ability btw multiple workers

                Log::info('There is no user in this job');
                $job->status = Job::STATUS_FINISHED;
                $job->save();
                return;

            }

            foreach ($job->jobUsers as $jobUser) {
                Log::info($jobUser->user_id);

                JobUser::where('job_id', $jobUser->job_id)
                    ->where('user_id', $jobUser->user_id)
                    ->lockForUpdate()  // exclusive lock
                    ->update(['status' => JobUser::STATUS_SENT]);
            }

        });

【讨论】:

  • 谢谢。这是我正在寻找的一个重要解决方案。能否请您更详细一点。什么是工作用户及其与工作的关系。谢谢
  • 我在一年前发布了这个,因为我记得“工作用户”是指工作中提到的所有用户作为等待电子邮件的用户。后来我意识到这种任务应该使用其他服务来完成,而不仅仅是 Laravel。尝试使用 RabbitMQ 并将用户的每个电子邮件发送作业作为队列中的一个任务推送,然后使用可以作为多个工作人员运行的 Laravel 命令监听该队列。使用这种结构,您可以避免任何锁定问题。
  • 谢谢。这将有很长的路要走。当我更新数据库上的字段时,我仍然必须使用 lockForUpdate() 方法。
【解决方案2】:

这可能不是答案,而是一些信息。

使用SELECT ... FOR UPDATE 语句时,您可能会观察到锁争用(死锁等)。

select … for update where x <= y

它使用

可以看到这个命令的问题:

SHOW ENGINE INNODB STATUS;

---TRANSACTION 72C, ACTIVE 755 sec
4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 0x7f84a78ba700, query id 163 localhost msandbox
TABLE LOCK table test.t trx id 72C lock mode IX
RECORD LOCKS space id 19 page no 4 n bits 80 index age of table test.t trx id 72C lock_mode X
RECORD LOCKS space id 19 page no 3 n bits 80 index GEN_CLUST_INDEX of table test.t trx id 72C lock_mode X locks rec but not gap
RECORD LOCKS space id 19 page no 4 n bits 80 index age of table test.t trx id 72C lock_mode X locks gap before rec

最后一行

如果您的事务中有很多间隙锁会影响并发性和性能,您可以通过两种不同的方式禁用它们:

1- Change the ISOLATION level to READ COMMITTED. In this isolation level, it is normal and expected that query results can change during a transaction, so there is no need to create locks to prevent that from happening.

2- innodb_locks_unsafe_for_binlog = 1. Disables the gap locks except for foreign-key constraint checking or duplicate-key checking.

https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/

【讨论】:

  • 所有这些可能会有所帮助,谢谢。但我真的很讨厌弄乱 laravel 核心。我相信这应该以某种方式“开箱即用”解决。尽管我可以对我的代码或工作流程进行一些“良好实践”的更改以避免该问题。
猜你喜欢
  • 2015-01-07
  • 2016-12-30
  • 2011-05-05
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
  • 2015-08-03
  • 2018-03-24
  • 1970-01-01
相关资源
最近更新 更多