Блокування в Postgres для комбінації UPDATE / INSERT


11

У мене дві таблиці. Один - таблиця журналів; інший, по суті, містить купонні коди, які можна використовувати лише один раз.

Користувачеві необхідно мати можливість викупити купон, який вставить рядок у журнальну таблицю та позначить купон як використаний (оновивши usedстовпчик до true).

Природно, тут очевидний стан гонки / проблема безпеки.

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

Чи є кращий спосіб у Postgres зробити це? Зокрема, я стурбований тим, що блокування є глобальним, але це не повинно бути - мені дійсно потрібно лише переконатися, що ніхто не намагається ввести саме цей код, тож можливо, блокування на рівні рядків спрацювало?

Відповіді:


15

Я чув про проблеми з одночасністю, як у MySQL, раніше. У Postgres не так.

Досить вбудованих блоків на рівні рядків на рівні READ COMMITTEDізоляції транзакцій за замовчуванням .

Я пропоную одне твердження з CTE, що модифікує дані (те, чого у MySQL також немає), оскільки зручно передавати значення з однієї таблиці в іншу безпосередньо (якщо вам це потрібно). Якщо вам нічого не потрібно з couponтаблиці, ви можете також використовувати транзакцію з окремими UPDATEі INSERTвиписками.

WITH upd AS (
   UPDATE coupon
   SET    used = true
   WHERE  coupon_id = 123
   AND    NOT used
   RETURNING coupon_id, other_column
   )
INSERT INTO log (coupon_id, other_column)
SELECT coupon_id, other_column FROM upd;

Має бути рідкісною річ, що більше однієї транзакції намагаються викупити той самий купон. У них є унікальний номер, чи не так? Більше однієї транзакції, яка намагається здійснити в той самий момент часу, має бути набагато рідше. (Можливо, помилка програми чи хтось намагається грати в систему?)

Як би там не було, UPDATEєдиний успіх здійснює саме одна транзакція, незважаючи ні на що. UPDATEНабуває блокування на рівні рядків на кожній цільовій рядку перед оновленням. Якщо паралельна транзакція намагається виконати UPDATEтой самий рядок, вона побачить блокування в рядку і чекатиме завершення ( ROLLBACKабо COMMIT) блокування транзакції , а потім буде першою у черзі блокування:

  • Якщо це здійснено, перевірте умову. Якщо це все-таки NOT usedзамкніть рядок і продовжуйте. В іншому випадку UPDATEтепер не знайдеться відповідного рядка і нічого не робить, не повертаючи жодного рядка, тому INSERTтакож нічого не робить.

  • Якщо відкотився, заблокуйте рядок і продовжуйте.

Немає потенціалу для перегонів .

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

INSERTЄ безтурботним. Якщо з якихось помилок coupon_idвже є в logтаблиці (і у вас є UNIQUE або PK обмеження log.coupon_id), вся транзакція буде скасована назад після унікального порушення. Вказує на незаконне стан у вашій БД. Якщо вищезазначене твердження є єдиним способом запису в logтаблицю, це ніколи не повинно виникати.


Це дійсно має бути рідкісна річ, коли більше однієї транзакції намагаються викупити один і той же код, але ваші підозри є правильними в тому, що це відбувається виключно тоді, коли хтось намагається грати в систему. Дякую за це - CTE були великою нічиєю для мене в переході до Postgres, але я не усвідомлював, що неявна блокування буде достатньою для цього.
Роб Міллер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.