2016-10-07 3 views
1

Я создаю «систему обслуживания бедных людей» с использованием MySQL. Это отдельная таблица, содержащая задания, которые необходимо выполнить (имя таблицы - queue). У меня есть несколько процессов на нескольких машинах, чья задача заключается в вызове fetch_next2 sproc, чтобы получить элемент из очереди.Блокировка строк в строке и атомные обновления

Весь смысл этой процедуры заключается в том, чтобы мы никогда не позволяли двум клиентам получать ту же работу. Я думал, что с помощью SELECT .. LIMIT 1 FOR UPDATE я бы заблокировал одну строку, чтобы я мог убедиться, что она была обновлена ​​только одним вызывающим абонентом (обновлено так, что оно больше не соответствует критериям SELECT, используемым для фильтрации заданий, которые являются «ГОТОВЫ «для обработки»).

Может ли кто-нибудь сказать мне, что я делаю неправильно? У меня просто были случаи, когда одна и та же работа была отдана двум различным процессам, поэтому я знаю, что она не работает должным образом. :)

CREATE DEFINER=`masteruser`@`%` PROCEDURE `fetch_next2`() 
BEGIN 
    SET @id = (SELECT q.Id FROM queue q WHERE q.State = 'READY' LIMIT 1 FOR UPDATE); 

    UPDATE queue 
    SET State = 'PROCESSING', Attempts = Attempts + 1 
    WHERE Id = @id; 

    SELECT Id, Payload 
    FROM queue 
    WHERE Id = @id; 
END 
+0

Почему так у меня проблемы с построением очереди бедных? Почему бы не использовать настоящую очередь. Лот меньше пота – middlestump

+0

@middlestump: Но это меньше 10 строк кода. :) Кроме того, мне бы очень хотелось понять, как эта строка-блокировка работает в MySQL для других проектов. – skb

ответ

1

Код для ответа:

CREATE DEFINER=`masteruser`@`%` PROCEDURE `fetch_next2`() 
BEGIN 
    SET @id := 0; 
    UPDATE queue SET State='PROCESSING', Id=(SELECT @id := Id) WHERE State='READY' LIMIT 1; 

    #You can do an if @id!=0 here 
    SELECT Id, Payload 
    FROM queue 
    WHERE Id = @id; 
END 

Проблема с тем, что вы делаете, что не существует атомная группировка для операций. Вы используете SELECT ... FOR UPDATE syntax. Документы говорят, что он блокирует «чтение данных на определенных уровнях изоляции транзакций». Но не все уровни (я думаю). Между вашим первым SELECT и UPDATE может быть другой SELECT из другого потока. Вы используете MyISAM или InnoDB? MyISAM может не поддерживать его.

Самый простой способ убедиться, что это работает должным образом - lock the table.


[править] Метод я описываю здесь больше времени, чем при использовании метода Id=(SELECT @id := Id) в приведенном выше коде.

Другой метод должен был бы сделать следующее:

  1. Есть столбец, который обычно устанавливается на 0.
  2. Делают «UPDATE ... SET ColName = UNIQ_ID WHERE ColName = 0 LIMIT 1. Это гарантирует, что только один процесс может обновить эту строку, а затем получить ее через SELECT. (UNIQ_ID не является функцией MySQL, просто переменной)

Если вам нужен уникальный идентификатор, вы можете использовать таблица с auto_increment только для этого.


Вы также можете вид сделать это с транзакциями. Если вы начинаете транзакцию в таблице, запустите UPDATE foobar SET LockVar=19 WHERE LockVar=0 LIMIT 1; из одного потока и сделайте то же самое в другом потоке, второй поток будет ждать завершения первого потока, прежде чем он получит свою строку. Тем не менее, это может быть полная операция блокировки таблицы.

+0

Спасибо. Я использую InnoDB. Чтобы получить награду за награду, не могли бы вы предоставить код хранимой процедуры для предлагаемых вариантов? – skb

+0

Есть ли какой-то конкретный метод тех, которые я перечислял, которые вы хотели бы мне сделать? Полный стол блокировки будет самым простым. – Dakusan

+0

Поскольку я использую InnoDB, я предполагаю, что он заблокирует только одну строку, не так ли?«Если вы используете InnoDB, он заблокирует только строку, с которой вы работаете». Эти варианты звучат хорошо для меня. – skb

Смежные вопросы