限制一次只能由一个脚本使用的表行

时间:2019-09-02 13:58:24

标签: php mysql locking innodb

我有一个表tableName,其中包含一些要使用的名称:

id    name    in_use
1     name1   0
2     name2   0

我的脚本使用in_use = 0从该表中获取名字:

SELECT name FROM tableName WHERE in_use = 0 ORDER BY id

,然后将获得的行设置为in_use = 1

UPDATE tableName set in_use = 1 WHERE id = '$idObtained'

脚本完成后,它设置了in_use = 0

UPDATE tableName set in_use = 0 WHERE id = '$idObtained'

问题是,当脚本将在同一瞬间多次执行时,此表中的两个或多个选择可能会获得相同的名称。

我的目标是避免两个脚本在执行时获得同一行。

有没有一种方法可以使用PHP / MySQL?

在选择之前锁定表并在更新之后将其解锁吗?

脚本(代码只是草稿。只有在PHP和MySQL中可能需要时,我才会进行开发):

<?php
    // ...

    // TODO: lock the raw, if found
    $query = "SELECT id, name
    FROM tableName
    WHERE in_use = 0
    ORDER BY id";
    $stmt = $connection->prepare($query);
    $stmt->execute();
    $data = $stmt->fetch();

    if(!empty($data['id'])) {

        $query = "UPDATE tableName
        SET in_use = 1
        WHERE id = '$data[id]'";
        $stmt = $connection->prepare($query);
        $stmt->execute();

        // do some with $data['name]

        $query = "UPDATE tableName
        SET in_use = 0
        WHERE id = '$data[id]'";
        $stmt = $connection->prepare($query);
        $stmt->execute();

        // TODO: unlock the raw
    }
?>

非常感谢。

1 个答案:

答案 0 :(得分:3)

要解决您的问题

BEGIN;
SELECT id, name FROM tableName WHERE in_use = 0 ORDER BY id
                  FOR UPDATE;   -- Add this 
$idObtained = ...
UPDATE tableName set in_use = 1 WHERE id = '$idObtained';
COMMIT;

稍后,在完成该行的工作后:

UPDATE tableName set in_use = 0 WHERE id = '$idObtained';

讨论:

FOR UPDATE防止另一个连接在SELECTUPDATE之间进行潜行,并且抓住该行。

如果此行上的操作仅需几秒钟,则此技巧已过时。因此,我假设这将花费很长时间,因此需要进行两次单独的交易-一项是“抢”行,另一项是“释放”行。

请注意,我没有将BEGINCOMMIT放在“发布” UPDATE周围。任何独立查询都是其自身的事务(假设autocommit=ON)。因此,仅通过单个查询即可轻松实现发布。

替代方法

如果我们可以设计一条指令来查找“空闲”行并“抓住”它,则有可能“简化”“抓取”。一种方法是包括一个额外的列,该列说明每个抓取的行都由哪个连接“拥有”。但是,让我们简单地更改set_in_use的工作方式。假设您每次连接最多只能抓取一行,CONNECTION_ID()很方便:

$conn = `SELECT CONNECTION_ID()`

UPDATE tbl SET in_use_by = $conn
    WHERE in_use_by IS NULL
    ORDER BY id
    LIMIT 1;      -- grab only 1 row
SELECT ... FROM tbl WHERE in_use_by = $conn;   -- Get the data to process

... process ...

UPDATE tbl SET in_use_by = NULL WHERE in_use_by = $conn;

使用此技术,不需要显式事务。 autocommit=ON可以正常工作。

此技术使您能够查看谁拥有哪些物品。 (并不是说您可以利用这些知识做很多事情。)

(可能还有其他方法。)