【问题标题】:Select and update on rows of a table at the same time in MySQL?在 MySQL 中同时选择和更新表的行?
【发布时间】:2015-09-21 12:41:37
【问题描述】:

我已经完成了几乎所有与此类似的现有问题,但没有找到我问题的答案。抱歉,如果我错过了已经发布的问题,这些问题会回答这个问题。

我有一个 MySQL 表,用作作业队列。有多个工人从这些表中读取作业。

挑战在于如何在表上使用 MySQL 查询来实现这一点。

我必须选择行并同时更新作业状态。这应该是自动的,所以没有工人得到已经在处理的工作。

我想自动运行以下代码(伪代码):

select name, job_type from jobs where job_status = "created" limit 10;

foreach row {
    update table jobs set job_status = "processing" where id = '$id';
}

这在 MySQL 中是否可以使用查询/存储过程/光标?

【问题讨论】:

  • 在这个任务中使用 MySQL 不是一个好主意;因为您必须执行锁定操作来处理竞争条件(由于多个工作人员)并且有时会陷入死锁。

标签: mysql


【解决方案1】:

我相信这是您的情况的解决方案:

update jobs set job_status = "processing"
where id = '$id'
and job_status = "created"

【讨论】:

  • 非常感谢您的回复!!查询应在更新所有选定行的作业状态后返回行。您的查询只是按 id 更新已知行。
【解决方案2】:

这些技术依赖于事务,因此请确保关闭自动提交。出于性能和数据完整性的原因,您无论如何都不应该使用自动提交。

这也依赖于使用 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。考虑获得真正的排队服务。

【讨论】:

  • 非常感谢您的回复.. 现在看起来确实可行。这个 select ... for update 是否也会锁定读取的行?我对光标没有很好的经验,你说的扔光标是什么意思。对于第二种方法(一次处理单个作业),我们需要运行三个单独的查询,对吗?
  • @SachinPatil 我弄错了,SELECT FOR UPDATE 在事务期间独占锁定。我已经广泛更新了答案。
  • 非常感谢,您已经详细说明了。您已经打开了多种方法来实现这一点,我更倾向于一次处理单个作业而没有锁定,正如您在第二种方法中提到的那样。
猜你喜欢
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
  • 2018-09-19
  • 2015-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多