如何在 mysql 中使用 UPDATE 自动声明行或资源

发布于 2024-08-25 14:59:11 字数 734 浏览 3 评论 0原文

我有一个资源表(比如说汽车),我想以原子方式声明它。然后我想要有关我刚刚声明的资源的信息。

如果每个用户只能使用一个资源,我可以执行以下技巧:

UPDATE cars SET user = 'bob' WHERE user IS NULL LIMIT 1
SELECT * FROM cars WHERE user = 'bob'

这样,我可以原子地声明该资源,然后我可以看到我刚刚声明了哪一行。

当“鲍勃”可以认领多辆车时,这不起作用。我意识到我可以获取鲍勃已经认领的汽车列表,认领另一辆,然后再次SELECT看看发生了什么变化,但这感觉很黑客。

我想知道的是,是否有某种方法可以查看我刚刚用上次更新更新了哪些行?

如果做不到这一点,是否还有其他技巧可以自动声明一行?我真的想避免使用SERIALIZABLE隔离级别。如果我这样做:

1 SELECT id FROM cars WHERE user IS NULL
2 <here, my PHP or whatever picks a car id>
3 UPDATE cars SET user = 'bob' WHERE id = <the one i picked>

REPEATABLE READ 在这里就足够了吗?换句话说,我能否保证其他一些事务不会声明我的软件在第 2 步中选择的行?

i have a table of resources (lets say cars) which i want to claim atomically. I then want information about which resource I just claimed.

If there's a limit of one resource per one user, i can do the following trick:

UPDATE cars SET user = 'bob' WHERE user IS NULL LIMIT 1
SELECT * FROM cars WHERE user = 'bob'

This way, I claim the resource atomically and then I can see which row I just claimed.

This doesn't work when 'bob' can claim multiple cars. I realize I can get a list of cars already claimed by bob, claim another one, and then SELECT again to see what's changed, but that feels hackish.

What I'm wondering is, is there some way to see which rows I just updated with my last UPDATE?

Failing that, is there some other trick to atomically claiming a row? I really want to avoid using SERIALIZABLE isolation level. If I do something like this:

1 SELECT id FROM cars WHERE user IS NULL
2 <here, my PHP or whatever picks a car id>
3 UPDATE cars SET user = 'bob' WHERE id = <the one i picked>

would REPEATABLE READ be sufficient here? In other words, could I be guaranteed that some other transactions won't claim the row my software has picked during step 2?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

你是我的挚爱i 2024-09-01 14:59:11
UPDATE cars SET user = 'bob' WHERE id = 123 AND user IS NULL;

更新查询返回更改的行数。如果没有更新,您就知道该车已被其他人认领。

或者,您可以使用 SELECT ... FOR UPDATE。

UPDATE cars SET user = 'bob' WHERE id = 123 AND user IS NULL;

The update query returns the number of changed rows. If it has not updated any, you know the car has already been claimed by someone else.

Alternatively, you can use SELECT ... FOR UPDATE.

坏尐絯 2024-09-01 14:59:11

我不确定为什么这些答案中有这么多错误信息,但答案很简单(即使是高级 SQL 主题)。这就是几乎所有 RDBMS 中“锁定”的用途。确切的语法取决于供应商和版本,有些提供尝试向用户隐藏锁定的语法(通常当选择和更新都在同一查询中时)。

对于 MySQL,您首先要使用 SELECT ... FROM ... FOR UPDATE;,它告诉数据库在它返回的每条记录上设置独占锁。

重要不要锁定超出绝对需要的行!使“SELECT FOR UPDATE”查询尽可能细化,并自由使用“WHERE”和“LIMIT”子句。

此后,当与数据库的同一连接对先前锁定的同一行发出 UPDATE ... 时,该锁定将被释放,其他人可以再次访问该行。

假设您有一个作业队列,其中有一个“状态”字段,用于设置每个作业的进度状态。 0 表示已排队,1 表示正在进行,2 表示完成,3 表示失败,等等。

每个运行者都可以通过发出以下命令自动获取要运行的作业(以便没有两个运行者尝试执行同一个作业) :

SELECT ID, * FROM JOBS WHERE STATUS = 0 LIMIT 1 FOR UPDATE;

然后

UPDATE JOBS SET STATUS = 1 WHERE JOBS.ID = X;

它就可以运行作业,并在完成后更新数据库:

UPDATE JOBS SET STATUS = [2|3] WHERE JOBS.ID = X;

重要

来自 MySQL 文档:

当事务提交或回滚时,由 LOCK IN SHARE MODE 和 FOR UPDATE 查询设置的所有锁都会被释放。

如果启用自动提交,SELECT FOR UPDATE 不会锁定记录。禁用自动提交或(最好)使用 START TRANSACTION;选择...从...进行更新;更新 ...;结束交易;

I'm not sure why there is so much misinformation in these answers, but the answer is straightforward (even if an advanced SQL topic). This is what "locking" is for in pretty much any RDBMS. The exact syntax depends on the vendor and version, and some offer syntax that tries to hide the lock from the user (typically when both the select and the update are in the same query).

For MySQL, you would first use the SELECT ... FROM ... FOR UPDATE; which tells the database to set an exclusive lock on each record it returns.

Important don't lock more rows than you absolutely need to! Make the "SELECT FOR UPDATE" query as granular as you can, with liberal usage of "WHERE" and "LIMIT" clauses.

Afterwards, when the same connection to the database issues an UPDATE ... on the same rows that were previously locked, that lock is released and others may access that row once more.

So let's say you have a job queue, with a field "STATUS" that is used to set the progress state of each job. 0 is for queued, 1 is for in progress, and 2 is for done, 3 is for failed, etc.

Each runner could atomically obtain a job to run (so that no two runners try to work the same job) by issuing the following:

SELECT ID, * FROM JOBS WHERE STATUS = 0 LIMIT 1 FOR UPDATE;

then

UPDATE JOBS SET STATUS = 1 WHERE JOBS.ID = X;

then it can run the job, and update the database when done:

UPDATE JOBS SET STATUS = [2|3] WHERE JOBS.ID = X;

Important

From the MySQL documentation:

All locks set by LOCK IN SHARE MODE and FOR UPDATE queries are released when the transaction is committed or rolled back.

SELECT FOR UPDATE does not lock records if autocommit is enabled. Either disable autocommit or (preferably) using START TRANSACTION; SELECT ... FROM ... FOR UPDATE; UPDATE ...; END TRANSACTION;

热鲨 2024-09-01 14:59:11
update cars  
set @id = id, user= 'bob' 
where user is null

保证是原子的,@id 会告诉你更新的最后一行是什么。

update cars  
set @id = id, user= 'bob' 
where user is null

is guaranteed to be atomic and @id will tell you what was the last row you updated.

贩梦商人 2024-09-01 14:59:11

您可以使用的一件事是 SELECT FOR UPDATE。这将让您进行选择,了解您选择的内容,然后更新这些值。当事务完成时,锁被释放。

<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "test");

mysqli_autocommit($link, FALSE);

$result = mysqli_query($link, "SELECT id FROM cars WHERE user IS NULL");

// do something with the results

mysqli_query($link, "UPDATE cars SET user = 'bob' WHERE id = <the one that was picked>");

mysqli_commit($link);

mysqli_close($link);
?>

One thing you can use is a SELECT FOR UPDATE. This will let you do your select, know what you selected and then update those values. The lock is released when the transaction is complete.

<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "test");

mysqli_autocommit($link, FALSE);

$result = mysqli_query($link, "SELECT id FROM cars WHERE user IS NULL");

// do something with the results

mysqli_query($link, "UPDATE cars SET user = 'bob' WHERE id = <the one that was picked>");

mysqli_commit($link);

mysqli_close($link);
?>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文