Як правильно реалізувати оптимістичне блокування в MySQL


13

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

Наша команда зробила висновок, що ми повинні зробити №4 нижче, інакше є ризик, що інший потік може оновити ту саму версію запису, але ми хотіли б підтвердити, що це найкращий спосіб зробити це.

  1. Створіть поле таблиці на таблиці, для якої слід використовувати оптимістичне блокування, наприклад, наприклад, ім'я стовпця = "версія"
  2. У розділі "Вибір" обов'язково додайте стовпець версії та занотуйте версію
  3. Після наступного оновлення запису оператор оновлення повинен видавати "де версія = X", де X - версія, яку ми отримали у №2, і встановити поле версії під час цього оператора оновлення на X + 1
  4. Виконайте SELECT FOR UPDATEзапис, який ми збираємось оновити, щоб ми серіалізували, хто може вносити зміни до запису, який ми намагаємось оновити.

Для уточнення ми намагаємося не допустити, щоб два потоки, які вибирають одну і ту ж запис у тому ж часовому вікні, де вони захоплюють одну й ту саму версію запису, не перезаписували б один одного, якщо вони намагались би одночасно спробувати оновити запис. Ми вважаємо, що якщо ми не зробимо №4, існує ймовірність, що якщо обидва потоки одночасно введуть відповідні транзакції (але ще не опублікували оновлення), коли вони перейдуть на оновлення, другий потік, який використовуватиме ОНОВЛЕННЯ ... де версія = X буде працювати на старих даних.

Чи правильно ми думаємо, що ми повинні робити це песимістичне блокування під час оновлення, навіть якщо ми використовуємо поля версії / оптимістичне блокування?


В чому проблема? Ви збільшуєте номер версії за допомогою UPDATE, тоді другий ОНОВЛЕННЯ буде невдалим, оскільки номер версії не такий, як коли він був прочитаний - що саме ви хочете.
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), що переможець, тепер ви можете побачити змінений рядок (як і раніше не видалений, тому його не видно для інших транзакцій) з a SELECT * FROM test WHERE id = 1.

Тепер COMMITу сесії переможців, скажімо (A).

(B)отримає блокування та продовжить оновлення. Однак версія більше не збігається, тому вона не змінить рядків, про що повідомляє результат підрахунку рядків. Лише одна UPDATEмала будь-який ефект, і клієнтська програма чітко бачила, що UPDATEвдалося, а яке - не. Подальше блокування не потрібно.

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


Якби ви НЕ додали поле рядка версії, то вам потрібно буде , SELECT ... FOR UPDATEщоб мати можливість надійно забезпечити порядок.

Якщо ви задумаєтесь над цим, a SELECT ... FOR UPDATEє абсолютно зайвим, якщо ви негайно робите UPDATEбез повторного використання даних з SELECTверсії або якщо ви використовуєте версію версій. UPDATEВізьме замок в будь-якому випадку. Якщо хтось інший оновить рядок між вашим читанням та наступним записом, ваша версія більше не збігатиметься, тому оновлення не вдасться. Ось як працює оптимістичне блокування.

Мета SELECT ... FOR UPDATE:

  • Керувати замовленням блокування, щоб уникнути тупикових ситуацій; і
  • Щоб розширити проміжок блокування рядків, коли ви хочете прочитати дані з рядка, змініть їх у програмі та напишіть нову рядок, що базується на оригінальній, не використовуючи SERIALIZABLEізоляцію чи версію версій.

Не потрібно використовувати як оптимістичне блокування (версія версії), так і SELECT ... FOR UPDATE. Використовуйте те чи інше.


Дякую Крейгу. Ви мали рацію - розробник помилився. Дякуємо, що провели цей тест.
BestPractices

Що з сервером SQL? Чи завжди блокування, придбане в оновленому рядку, незалежно від рівня ізоляції транзакцій?
plalx

@plalx Ну, що говорить документація? Що станеться, якщо запустити інтерактивний тест так само, як цей?
Крейг Рінгер

@CraigRinger, що буде, якщо B отримати блокування перед 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.