MySQL中的竞争条件 - 防止多次插入

时间:2015-04-06 20:23:59

标签: mysql sql transactions innodb race-condition

假设我有以下情况:一个用户只能有一个活动任务。

因此,如果我想为id为2的用户添加任务,我需要这样做:

SELECT * FROM tasks WHERE active=1 AND id_user = 2;

如果没有我可以做的记录

INSERT INTO tasks(id_user, active) VALUES(2,1);

问题是:如果我将select和insert放入事务(并使用InnoDB),我可以确定用户不会创建2个活动任务吗?

上述情况已经简化,但有人告诉我,在MySQL备份期间(mysql插入服务器以某种方式排队)用户创建了许多活动任务,这些任务不允许使用这样的代码(如果事务是,我目前还不知道用过的)。根据“故事”,唯一可行的解​​决方案是使用队列(可能是Beanstalkd)在完成另一个任务后运行任务。

所以问题是 - 是否有可能发生类似这样的事情,如果使用MySQL事务处理工作解决方案,或者在这种情况下可能会使用其他解决方案?

修改

这里的问题是我无法为列添加唯一键,我也无法更新记录。为了使其更准确,真实的情况是:

在表格任务中有:

id - 主键,自动递增 id_user - 用户的id 开始日期 end_date - nullable(如果任务未完成,则设置为null)

一个用户只能有一个未完成的任务(end_date为空),因此当用户至少有一个具有空日期的任务时,无法创建该用户的新任务。但是我在旧数据库上运行,如果end_date设置为null,很可能会有很多相同用途的记录,所以我不能在这里使用唯一的,每次插入之前我需要先检查

SELECT * FROM tasks WHERE end_date IS NOT null AND id_user = 2

如果没有记录,那么我可以为用户创建新任务。

将来很有可能在创建新任务之前添加更多条件以进行验证。

2 个答案:

答案 0 :(得分:0)

了解InnoDB"交易"。了解SELECT ... FOR UPDATE

但是,对于这种情况,可以通过以下方式避免这两种情况:

UPDATE tasks SET active = 1 WHERE id_user = 2;   -- not adequate

然后检查RowsAffected:0表示它没有激活。

使用修改,抓住我的UPDATE;回到交易。在伪代码中:

BEGIN;
$active = SELECT active FROM tasks WHERE id_user = 2 FOR UPDATE;
if ($active) exit;
INSERT INTO tasks (id_user, active, task)
    VALUES (2, 1, 'foo')
    ON DUPLICATE KEY UPDATE  active = 1 task = 'foo';
COMMIT;

这假设您有PRIMARY KEY(id_user)

答案 1 :(得分:0)

如果您的UNIQUE索引可以防止重复,请执行以下操作:

INSERT IGNORE INTO tasks (id_user, active) VALUES (2,1)

这将忽略有关重复条目的警告。这具有仅添加一次记录的效果。