我有一个表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
}
?>
非常感谢。
答案 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
防止另一个连接在SELECT
和UPDATE
之间进行潜行,并且也抓住该行。
如果此行上的操作仅需几秒钟,则此技巧已过时。因此,我假设这将花费很长时间,因此需要进行两次单独的交易-一项是“抢”行,另一项是“释放”行。
请注意,我没有将BEGIN
和COMMIT
放在“发布” 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
可以正常工作。
此技术使您能够查看谁拥有哪些物品。 (并不是说您可以利用这些知识做很多事情。)
(可能还有其他方法。)