Как правильно реализовать оптимистическую блокировку в MySQL


13

Как правильно реализовать оптимистическую блокировку в MySQL?

Наша команда пришла к выводу, что мы должны сделать № 4 ниже, иначе есть риск, что другой поток может обновить ту же версию записи, но мы хотели бы проверить, что это лучший способ сделать это.

  1. Создайте поле версии в таблице, для которой вы хотите использовать оптимистическую блокировку, например, столбец name = "версия"
  2. При выборе обязательно включите столбец версии и запишите версию
  3. При последующем обновлении записи оператор обновления должен выдать «где версия = X», где X - версия, которую мы получили в # 2, и установить поле версии во время этого оператора обновления на X + 1.
  4. Выполните SELECT FOR UPDATEзапись, которую мы собираемся обновить, чтобы сериализовать, кто может вносить изменения в запись, которую мы пытаемся обновить.

Чтобы уточнить, мы пытаемся предотвратить перезапись друг друга двумя потоками, которые выбирают одну и ту же запись в одном и том же временном окне, где они захватывают одну и ту же версию записи, если они попытаются обновить запись одновременно. Мы считаем, что если мы не выполним # 4, есть вероятность, что, если оба потока одновременно введут свои соответствующие транзакции (но еще не выпустили свои обновления), когда они пойдут на обновление, второй поток будет использовать UPDATE. ... где версия = X будет работать со старыми данными.

Правильны ли мы, думая, что мы должны делать эту пессимистическую блокировку при обновлении, даже если мы используем поля версий / оптимистическую блокировку?


В чем проблема? Вы увеличиваете номер версии с помощью своего ОБНОВЛЕНИЯ, тогда второе ОБНОВЛЕНИЕ не будет выполнено, потому что номер версии не совпадает с тем, когда он был прочитан - это то, что вам нужно.
AndreKR

Ты уверен? Непонятно, что если вы не установите уровень изоляции транзакции на конкретную настройку, вы увидите, что другие потоки обновятся. Если вы оба вводите транзакцию одновременно, второй поток может очень хорошо видеть данные OLD, когда он выполняет обновление. MySQL не так надежен на арене ACID, как, например, Oracle, поэтому ищет наилучший способ реализации оптимистической блокировки в MySQL, которая предотвратит грязное чтение / обновление.
BestPractices

Но тогда транзакция все равно потерпит неудачу во время коммита, верно?
AndreKR

Признаки того, что можно было бы сделать выбор для обновления, чтобы справиться с этой ситуацией: dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
BestPractices

@BestPractices Вам нужна либо SELECT ... FOR UPDATE оптимистическая блокировка, либо версионность строк, а не обе. Подробности смотрите в ответе.
Крейг Рингер

Ответы:


17

Ваш разработчик ошибается. Вам нужно либо SELECT ... FOR UPDATE или ряд версий, но не оба.

Попробуйте и посмотрите. Открытые три MySQL сессии (A), (B)и (C)в той же базе данных.

В (C)номере:

CREATE TABLE test(
    id integer PRIMARY KEY,
    data varchar(255) not null,
    version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;

И в том, (A)и в другом (B)выполните UPDATEтестирование и настройку версии строки, изменив winnerтекст в каждом, чтобы вы могли увидеть, какой сеанс какой:

-- In (A):

BEGIN;
UPDATE test SET data = 'winnerA',
            version = version + 1
WHERE id = 1 AND version = 0;

-- in (B):

BEGIN;
UPDATE test SET data = 'winnerB',
            version = version + 1
WHERE id = 1 AND version = 0;

Теперь (C), UNLOCK TABLES;чтобы снять блокировку.

(A)и (B)будет гоняться за блокировку строк. Один из них победит и получит замок. Другой заблокирует замок. Победитель, получивший блокировку, приступит к смене ряда. Предполагая, (A)что это победитель, теперь вы можете увидеть измененную строку (все еще незафиксированную, поэтому не видимую для других транзакций) с помощью SELECT * FROM test WHERE id = 1.

Теперь COMMITв сессии победителя, скажем (A).

(B)получит блокировку и продолжит обновление. Однако версия больше не совпадает, поэтому она не изменит строки, как указано в результате подсчета строк. Только один из UPDATEних имел какой-либо эффект, и клиентское приложение может четко видеть, что UPDATEудалось, а что - нет. Никакой дополнительной блокировки не требуется.

Смотрите логи сессии на pastebin здесь . Я использовал и mysql --prompt="A> "т. Д., Чтобы было легко заметить разницу между сессиями. Я скопировал и вставил вывод с чередованием во временной последовательности, так что это не полностью необработанный вывод, и, возможно, я мог допустить ошибки при копировании и вставке. Проверьте сами, чтобы увидеть.


Если бы вы не добавили поле строки версии, то вам нужно будет , SELECT ... FOR UPDATEчтобы иметь возможность надежно обеспечить порядок.

Если вы думаете об этом, SELECT ... FOR UPDATEэто совершенно излишним , если вы сразу делаете UPDATEбез данных повторного использования из SELECT, или , если вы используете строку управления версиями. В UPDATEлюбом случае взломает замок. Если кто-то еще обновит строку между вашим чтением и последующей записью, ваша версия больше не будет совпадать, поэтому ваше обновление не удастся. Вот как работает оптимистичная блокировка.

Целью SELECT ... FOR UPDATEявляется:

  • Управлять порядком блокировки, чтобы избежать тупиков; и
  • Чтобы расширить диапазон блокировки строк, когда вы хотите прочитать данные из строки, измените их в приложении и напишите новую строку, основанную на исходной, без необходимости использовать SERIALIZABLEизоляцию или управление версиями строк.

Вам не нужно использовать как оптимистическую блокировку (управление версиями строк), так и SELECT ... FOR UPDATE. Используйте один или другой.


Спасибо, Крейг. Вы были правы - разработчик ошибся. Спасибо за запуск этого теста.
BestPractices

А как насчет SQL-сервера? Всегда ли блокируется обновленная строка независимо от уровня изоляции транзакции?
plalx

@plalx Ну, что говорит документация? Что произойдет, если вы запустите интерактивный тест, как этот?
Крейг Рингер

@CraigRinger, что произойдет, если B получит блокировку перед фиксацией A, но после обновления A?
MengT

1
@MengT Не может, вот почему это замок.
Крейг Рингер

0
UPDATE tbl SET owner = $me,
               id = LAST_INSERT_ID(id)
    WHERE owner = ''
    LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE  tbl SET owner = '' WHERE id = $id;

Никакие блокировки (не таблицы, не транзакции) не нужны или даже желательны:

  • ОБНОВЛЕНИЕ атомное
  • LAST_INSERT_ID () является специфичным для сессии, следовательно, потокобезопасным.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.