【问题标题】:Race condition in MySQL - prevent multiple insertsMySQL 中的竞争条件 - 防止多次插入
【发布时间】:2015-04-06 20:23:58
【问题描述】:

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

因此,如果我想为 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 - 可为空(如果任务未完成,则设置为空)

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

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

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

也很有可能在未来添加更多此选择的条件以在创建新任务之前进行验证。

【问题讨论】:

    标签: mysql sql transactions innodb race-condition


    【解决方案1】:

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

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

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

    【讨论】:

    • 添加UNIQUE 列是执行此操作的方法。否则,您将不得不编写一些丑陋、复杂的过程并将其包装在事务中。
    【解决方案2】:

    了解 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)

    【讨论】:

      猜你喜欢
      • 2016-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-23
      • 1970-01-01
      相关资源
      最近更新 更多