«FOR UPDATE» v / s «LOCK IN SHARE MODE»: разрешить одновременным потокам читать обновленное значение «state» заблокированной строки - PullRequest
5 голосов
/ 27 января 2012

У меня есть следующий сценарий:

  • Пользователь X входит в приложение из местоположения lc1: вызовите его Ulc1
  • Пользователь X (был взломан, или его знакомый знает свои учетные данные для входа в систему, или он просто входит в систему из другого браузера на своей машине и т. д. (вы получили точку) одновременно входит в систему из местоположения lc2: позвоните по этому номеру Ulc2

Я использую основной сервлет, который:
- получает соединение из пула базы данных
- устанавливает для autocommit значение false
- выполняет команду, которая проходит через приложениеLayers: если все успешно, установите autocommit в true в операторе "finally" и закройте соединение.Иначе, если произойдет исключение, rollback ().

В моей базе данных (mysql / innoDb) у меня есть таблица «history» со столбцами строк:
id (первичный ключ) | username |дата |тема |заблокирован

Столбец «заблокирован» по умолчанию имеет значение «ложь» и служит в качестве флага, который отмечает, заблокирована ли конкретная строка или нет.
Каждая строка специфична для пользователя (как вы можете видеть из столбца имени пользователя)

Итак, вернемся к сценарию:
-> Ulc1 отправляет команду update его история из базы данных для даты "D" и темы "T".

-> Ulc2 отправляет команду same в update history из базы данных для то же самое дата "D" и то же самое тема "T" в точное время.

Я хочу реализовать блокировку mysql / innoDBсистема, которая разрешит любому потоку, поступающему, выполнить следующую проверку:

Является ли столбец "заблокированным" для этой строки, верно или нет?

  • если true, вернуть пользователю сообщение о том, что «он уже обновляет те же данные из другого местоположения»
  • , если не true (т.е. не заблокировано): пометить как заблокированноеи обновить, а затем сбросить блокировку на false после завершения.

Какой из этих двух методов блокировки mysql фактически позволит 2-му прибывающему потоку прочитать "обновленное" значение заблокированного столбца, чтобы решить, какое действие wtВзять?

Должен ли я использовать "ДЛЯ ОБНОВЛЕНИЯ" или "БЛОКИРОВКА В РЕЖИМЕ ОБМЕНА" ?

Этот сценарий объясняет, чего я хочу достичь:
- поток Ulc1 прибывает первым: столбец "locked" имеет значение false, задает для него значение true и продолжает процесс обновления
- поток Ulc2 прибывает, когда транзакция Ulc1 все еще находится в процессе, и хотя строка заблокирована с помощью функций innoDb,он не должен ждать, но на самом деле считывает «новое» значение заблокированного столбца, которое является «истиной», и поэтому фактически не должно ждать, пока транзакция Ulc1 не выполнит фиксацию, чтобы прочитать значение of «заблокированный» столбец (в любом случае к этому времени значение этого столбца уже будет сброшено в false).

Я не очень разбираюсь в 2 типах механизмов блокировки, что я понимаюпока что LOCK IN SHARE MODE позволяет другой транзакции читать заблокированную строку, а FOR UPDATE даже не позволяет читать.Но это чтение получает обновленное значение?или 2-й прибывающий поток должен дождаться, когда первый поток подтвердит выполнение, чтобы затем прочитать значение?

Любые рекомендации о том, какой механизм блокировки использовать для этого сценария, приветствуются.
Также, если есть лучший способ "проверить", заблокирована ли строка (кроме использования флага столбца true / false), пожалуйста, дайте мне знать об этом.
спасибо

SOLUTION
(пример псевдокода Jdbc на основе ответа @ Darhazer )

Таблица: [id (первичный ключ) | имя пользователя |дата |тема |заблокирован]

connection.setautocommit(false);
//transaction-1
PreparedStatement ps1 = "Select locked from tableName for update where id="key" and   locked=false);
ps1.executeQuery();

//transaction 2
PreparedStatement ps2 = "Update tableName set locked=true where id="key";
ps2.executeUpdate();
connection.setautocommit(true);// here we allow other transactions threads to see the new value

connection.setautocommit(false);
//transaction 3
PreparedStatement ps3 = "Update tableName set aField="Sthg" where id="key" And date="D" and topic="T";
ps3.executeUpdate();

// reset locked to false
 PreparedStatement ps4 = "Update tableName set locked=false where id="key";
ps4.executeUpdate();

//commit
connection.setautocommit(true);

1 Ответ

5 голосов
/ 02 февраля 2012

LOCK IN SHARE MODE позволит 2-му потоку прочитать значение, но фактическое значение будет тем, которое было до запроса (чтение передано) или до того, как была запущена транзакция (повторяемое чтение) (поскольку MySQL использует мультиверинг. и то, что должно быть видно во второй транзакции, определяется уровнем изоляции). Таким образом, если 1-я транзакция не зафиксирована во время чтения, будет прочитано старое значение.

В вашем сценарии лучше всего иметь 1 транзакцию, которая блокирует запись с помощью выбора для обновления, другая, чем работа с записью и третья транзакция при фиксации / откате, открывает запись.

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

Чтобы избежать тупика, убедитесь, что вы делаете select for update, используя уникальный индекс.

Пример кода:

connection.setautocommit(false);
//transaction-1
PreparedStatement ps1 = "Select locked from tableName for update where id="key" and   locked=false);
ps1.executeQuery();

//transaction 2
PreparedStatement ps2 = "Update tableName set locked=true where id="key";
ps2.executeUpdate();
connection.setautocommit(true); // here we allow other transactions / threads to see the new value

connection.setautocommit(false);
//transaction 3
PreparedStatement ps3 = "Update tableName set aField="Sthg" where id="key" And date="D" and topic="T";
ps3.executeUpdate();

// probably more queries

// reset locked to false
 PreparedStatement ps4 = "Update tableName set locked=false where id="key";
ps4.executeUpdate();

//commit
connection.setautocommit(true);
...