Php “基于Laravel 5.5 MySQL”;“排队”;以高请求量锁定数据库
关于MySQL数据库事务、锁定和Laravel雄辩的ORM,我遇到了一个超出我深度的问题。提前感谢您的帮助Php “基于Laravel 5.5 MySQL”;“排队”;以高请求量锁定数据库,php,mysql,laravel,locking,Php,Mysql,Laravel,Locking,关于MySQL数据库事务、锁定和Laravel雄辩的ORM,我遇到了一个超出我深度的问题。提前感谢您的帮助 拉威尔5.5 PHP 7.2.25 MySQL 8.0.13(InnoDB) 我的(简化的)问题是:我的MySQL数据库中有一个tasks表,一个用于检索下一个任务的端点,以及一个用于将任务标记为已完成或不活动的端点。tasks表的作用是充当一种队列,允许用户提取当前没有其他人正在处理的下一个可用任务(读取:task处于非活动状态)。如果任务被拉入,但在3小时内未进行更新,则检索端点应
- 拉威尔5.5
- PHP 7.2.25
- MySQL 8.0.13(InnoDB)
tasks
表,一个用于检索下一个任务的端点,以及一个用于将任务标记为已完成或不活动的端点。tasks
表的作用是充当一种队列,允许用户提取当前没有其他人正在处理的下一个可用任务(读取:task处于非活动状态)。如果任务被拉入,但在3小时内未进行更新,则检索端点应再次可以看到该任务(以防用户意外退出应用程序等)
我的问题出现在任务检索端点上。我在这里使用DB::transaction
和lockForUpdate()
来防止并发问题(即两个用户在同一个任务被标记为活动之前获取该任务)。当每秒请求量相对较低时,它会按预期工作,为每个请求正确地获取一个唯一的任务。但是,在更高的请求频率下,整个任务
表最终会锁定,请求开始超时,数据库在重新启动之前会变得无响应。这是我的密码:
// Select the next task in the queue
$task = DB::transaction(
function () {
$t = Task::where('finished', '=', false)
->where(function ($q) {
// Task must not be active for another user
$q->where('active', '=', false)
->orWhere('updated_at', '<', DB::raw('DATE_SUB(NOW(), INTERVAL 3 HOUR)'));
})
// Prefer tasks that haven't been seen yet
->orderBy('attempts')
->lockForUpdate()
->first();
if (null !== $t) {
// Set the task as active and increment attempts
$t->active = true;
$t->attempts++;
$t->save();
}
return $t;
}
);
//选择队列中的下一个任务
$task=DB::事务(
函数(){
$t=Task::where('finished','=',false)
->其中(函数($q){
//对于其他用户,任务不能处于活动状态
$q->where('active','=',false)
->orWhere('updated_at'、'orderBy('truments'))
->lockForUpdate()
->第一个();
如果(空!==$t){
//将任务设置为活动和增量尝试
$t->active=true;
$t->尝试次数++;
$t->save();
}
返回$t;
}
);
其他一些背景:
- 其他具有类似DB读取的更高频率的请求没有问题,因此这里的问题肯定与事务/锁定行为有关,而不是其他一些系统配置(MySQL连接、PHP线程等)
Eloquent模型具有标准的Laravel时间戳,因此Task
自动设置为updated_at
方法的一部分save()
我在这里是否有明显的遗漏,或者我是否完全偏离了基准?我正在尝试的是可能的,还是我需要从MySQL转移到更传统的排队系统(Redis、SQS等)?如果可以的话,我可以切换到原始SQL查询,而不是使用ORM。感谢您的指导!我不确定这一点,但建议太长,无法发表评论 我认为
lockForUpdate
方法可能会导致该行被锁定,甚至在下一次搜索时也会考虑。这意味着在提交上一次更新之前,一次搜索无法完成。(即,在第一次搜索中,您选择非活动的行A,然后在第二次搜索中,您查找所有非活动行,其中行A仍然是一行,但在解锁行A之前,您无法完成查询)
要解决此问题,您可以尝试添加跳过锁定的
(请参阅)。这将从第二次搜索中排除锁定的行A,并允许它在提交上一个事务之前返回结果
我在Laravel中找不到添加此项的方法,因此您需要使用原始查询生成器手动添加锁定子句,或者可能需要使用
->raw
在何处工作。表运行在什么引擎上?如果您不知道如何检查,可以在Tinker中运行此项:DB::select('show table status where name=?',['tasks'))
我询问的原因是InnoDB允许您一次锁定一行,而MyISAM要求您锁定整个表。如果我在这里出错,请纠正我,但我认为lockForUpdate
仅锁定该查询,而不锁定save()
query after。虽然这个名字很容易让人误解,但我认为它的工作方式与你想象的不一样。@lufc它是InnoDB;我会更新帖子,thanks@PtrTon我的理解是,它读取锁定行直到DB事务结束。很可能我错了,但如果是这样,我不确定如何正确使用它,或者alt是什么Ernactive会beAmazing--你完全正确。我以为查询是同时运行的,但你的断言是正确的,即每个查询都在等待前一个锁释放。我看到的问题一定是由于达到了数据库无法跟上的查询量。修复程序也比我预期的要简单得多o be:我刚刚不得不将->lockForUpdate()
更改为->lock('FOR UPDATE SKIP LOCKED')
。谢谢!