Как запросить и увеличить значение (счетчик) потокобезопасным способом? (избегайте условий гонки)

10

В таблице , где каждая строка имеет счетчик (только целое значение), мне нужно , чтобы получить текущее значение и увеличить его в то же самое время .

По сути, я хочу сделать это:

SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123

Но выполнение двух запросов, очевидно, не является потокобезопасным: несколько процессов, выполняющих одну и ту же операцию (в одной строке), могут получить одно и то же значение счетчика. Мне нужно , чтобы они все быть уникальным, так что каждый процесс будет получить фактическое значение тока и увеличить его на единицу.

Я могу придумать конструкцию, в которой я устанавливаю ручную блокировку для каждой строки, но мне интересно, есть ли более простой способ сделать это?

RocketNuts
источник
используя транзакции возможно?
ypercubeᵀᴹ

Ответы:

15

Операторы обновления прекрасно работают без выбора раньше! Поскольку отдельные операторы безопасны по определению, даже два запроса UPDATE, выполняемые одновременно, приведут к увеличению строки в два раза.

Если вы действительно хотите выбрать значение для своего PHP-скрипта, сделать что-то с ним, а затем хотите обновить это точное значение счетчика, вы можете сделать следующее:

BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;

Это запускает новую транзакцию, затем выбирает строки, которые вы хотите обновить, и блокирует их исключительно. Затем вы можете безопасно обновлять их, не беспокоясь о том, что другие клиенты меняют свое содержимое или даже получают доступ к заблокированным строкам. Наконец, вам нужно зафиксировать свои изменения.

Вы должны также прочитать кое-что об уровнях изоляции . Вы, вероятно, не хотите, чтобы значение, как READ UNCOMMITTEDуровень изоляции. Все остальное должно быть хорошо для этого варианта использования.

GhostGambler
источник
Я читал в других местах, что UPDATE table SET counter = counter + 1это достаточно атомно? Вы все еще нуждаетесь в заявлениях транзакции, окружающих это?
CMCDragonkai
@CMCDragonkai Ваш запрос сам по себе является атомарным, но если вы выбрали значение ранее и не использовали FOR UPDATEи транзакции, то выбранное вами значение может отличаться от того, которое использовалось в запросе на обновление. Моя комбинация запросов блокирует строку, как только значение выбрано, и поэтому гарантирует, что это точное значение счетчика будет использовано в запросе на обновление.
GhostGambler
Хорошо, но это только необходимо, если я делаю какую-либо работу, кроме увеличения, верно? Как бы то ни было, одиночный атомарный запрос на обновление достаточно хорош, если это все, что я хочу сделать?
CMCDragonkai
1
@CMCDragonkai Если вы не выполните другой запрос, который касается столбца, у вас все хорошо.
GhostGambler