这些技术依赖于事务,因此请确保关闭自动提交。出于性能和数据完整性的原因,您无论如何都不应该使用自动提交。
这也依赖于使用 InnoDB 表格式。 MyISAM 不支持行级锁定。
使用SELECT ... FOR UPDATE 对返回的行设置排他锁。在事务提交或回滚之前,锁将一直存在。
select id, name, job_type
from jobs
where job_status = "created"
limit 10
for update;
foreach row {
update table jobs set job_status = "processing" where id = '$id';
...process...
delete from jobs where id = '$id';
}
commit
除非您有充分的理由这样做,否则最好一次只抓住一排。这是一个简单、快速的查询,没有理由坚持 10 次。事实上,如果您坚持 10 次,您就无法在每次成功后提交。
select id, name, job_type
from jobs
where job_status = "created"
limit 1
for update;
update table jobs set job_status = "processing" where id = '$id';
...process the job...
delete from jobs where id = '$id';
commit
我们可以进一步减少它。无需将作业设置为处理,其他事务无论如何都不会看到更改。我们可以依赖排他锁。
select id, name, job_type
from jobs
where job_status = "created"
limit 1
for update;
...process $id...
delete from jobs where id = '$id';
commit
这是健壮的。如果您的工作人员崩溃,它的锁定将被移除,另一个工作人员可以再次尝试该工作。
或者,您可以一次更新一个。这需要getting the ID of the row you just updated。
SET @update_id := 0;
UPDATE jobs
SET job_status = "processing", id = (SELECT @update_id := id)
WHERE job_status = "created"
LIMIT 1;
SELECT @update_id;
...do work on @update_id...
DELETE FROM jobs WHERE id = @update_id
COMMIT
这依赖于UPDATE setting an exclusive lock on each updated row,其他事务将无法更新该行。这就是为什么一次处理一个是个好主意。
或者,添加queued 作业状态以及哪个进程拥有它。
UPDATE jobs
SET job_status = "queued", job_owner = "$pid"
WHERE job_status = "created" limit 10;
COMMIT;
SELECT name, job_type
FROM jobs
WHERE job_status = "queued"
AND job_owner = "$pid"
foreach row {
UPDATE jobs SET job_status = "processing" where id = '$id';
... process ...
DELETE FROM jobs WHERE id = '$id';
COMMIT;
}
这种方法的缺点是,如果工人死了,它仍然拥有工作。如果你有一个长期存在的主人来控制队列并将作业分配给工人,我只会推荐这种方法。
最后还有subtle problems with using MySQL as a queue。考虑获得真正的排队服务。