假设我有一张名为tasks
的表格。每项任务都有一个status
。我执行处于To Manage
状态的任务之一,将其置于In Management
状态,并运行创建任务的过程(可能需要几秒钟才能完成)。
在执行结束时,任务可能会返回To Manage
或Completed
状态,具体取决于是否必须再次运行该过程。
现在假设有几个进程同时运行此活动,以便完成或以其他方式同时处理多个不同的任务。
我想确保两个进程不会同时管理同一个任务。为实现此目的,应在交易中执行上述活动:
$db->beginTransaction(); /* transaction A */
/* Reads one task from the database (SELECT query with LIMIT 1) which is in the `To Manage` status and returns it */
$task = $tasks->getNextTask(); /* operation 1 */
/* Changes the status into the `In Management` status (UPDATE query) */
$task->changeStatusToManage(); /* operation 2 */
$db->commit();
$task->execute(); /* operation 3 */
我正在使用MySql数据库,该表是InnoDB,具有READ COMMITTED隔离级别:https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
我们说To Manage
状态只有一个任务。如果同时执行两个进程(P1和P2)并且transaction A
不存在,则可能发生以下情况:
Instant 1: (operation 1) P1 reads the task id 100 in `To Manage` status
Instant 2: (operation 1) P2 reads the task id 100 in `To Manage` status
Instant 3: (operation 2) P1 puts the task id 100 in the `In Management` status
Instant 4: (operation 2) P2 puts the task id 100 in the `In Management` status
Instant 5: (operation 3) P1 performs the task id 100
Instant 6: (operation 3) P2 performs the task id 100
但是,操作1-2-3实际上是在事务中执行的事实,这种情况应该是不可能的。
LOCK
来读取任务表,并在操作2完成后释放它?DB结构比上述结构复杂得多。当我更改任务状态时,我也会在另一个表上写一个日志。这是由代码(模型类)本身完成的。我有任务表,task_status表和任务上的外键以及task_status_change(这是日志表)。每个txn执行1次读取(获取任务),2次写入(更改状态和写入日志)。所以我需要执行与此类似的操作(伪代码):
BEGIN;
$id = SELECT task_id FROM task WHERE task_status_id = 1 LIMIT 1;
UPDATE task SET task_status_id = 2 WHERE task_id = $id;
INSERT INTO task_status_change SET task_id = $id, task_status_id = 2;
COMMIT;
正如我上面提到的,我正在使用READ COMMITED隔离级别。我试图同时启动两个进程,在同一个任务池上一起运行。
第一个进程选择的任务ID(ID和时间戳):
55 1496925510
274 1496925512
384 1496925512
589 1496925513
648 1496925513
1088 1496925513
1990 1496925513
第二个进程选择的任务ID(ID和时间戳):
55 1496925510
274 1496925512
589 1496925512
648 1496925513
810 1496925513
1088 1496925513
2049 1496925514
谢谢
答案 0 :(得分:1)
getNextTask
应该修改status
并获取它在单个事务中修改的任务的ID。一种方式(伪代码):
BEGIN;
$id = SELECT id ...
WHERE status = 'idle'
LIMIT 1 ... FOR UPDATE;
UPDATE ... SET status = 'management' WHERE id = $id
COMMIT;
根据您的表结构,可以在单个原子UPDATE
语句中执行事务。 (你没有提供太多细节。)
为每个状态转换做类似的事情。
这为长期运行的您的代码集提供了事务语义,只使用数据库中的一个status
。
这听起来像是一个“排队”机制。我有一句口头禅:“不要排队,只要这样做。”这意味着,无论何时执行任务,都可以更容易/更快/更简单地生成工作进程,而不是排队等等。