如何以线程安全的方式查询和增加一个值(计数器)?(避免竞态条件)

在一个表格中,每一行都有一个计数器(只是一个整数值),我需要同时获取当前值并增加它。 实际上,我想要做的是:
SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123
但是将其作为两个查询来执行显然不是线程安全的:多个进程对同一行进行相同操作可能会获得相同的计数器值。我需要它们都是唯一的,所以每个进程都会获得实际的当前值并将其递增一次。 我可以想到一种方法,即为每行实现一个手动锁定,但我想知道是否有更简单的方法?

使用事务可能吗? - ypercubeᵀᴹ
1个回答

更新语句在没有选择之前就可以正常工作!由于单个语句从定义上来说是安全的,即使同时执行两个UPDATE查询,也只会将行增加两次。 如果您实际上想要选择PHP脚本的值,并对其进行后续操作,然后想要更新此确切的计数器值,您可以按照以下步骤进行操作:
BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;
这将开始一个新的事务,然后选择要更新的行并独占锁定它们。您可以安全地更新这些行,而不必担心其他客户端更改其内容甚至访问已锁定的行。最后,您需要提交您的更改。 您还应该阅读一些关于隔离级别的内容。您可能不希望使用像“读取未提交”这样的隔离级别。对于这种用例,其他所有隔离级别都应该是可以接受的。

我在其他地方看到过UPDATE table SET counter = counter + 1被认为是足够原子性的。你还需要将其包裹在事务语句中吗? - CMCDragonkai
1@CMCDragonkai 你的查询本身是原子性的,但如果在选择值之前未使用FOR UPDATE和事务,那么你选择的值可能与更新查询中使用的值不同。我组合的查询在选择值后立即锁定行,因此确保在更新查询中使用这个确切的计数器值。 - Ulrich Thomas Gabor
好的,但是只有在我需要做除了递增之外的其他工作时才需要吗?就目前而言,如果我只想做这个,那么单独的原子更新查询就足够了? - CMCDragonkai
1@CMCDragonkai 如果你不执行其他涉及该列的查询,那就没问题了。 - Ulrich Thomas Gabor

  • 相关问题