JavaRush /Курси /SQL SELF /Обмеження та потенційні проблеми при роботі з транзакціям...

Обмеження та потенційні проблеми при роботі з транзакціями: LOCK, DEADLOCK

SQL SELF
Рівень 54 , Лекція 1
Відкрита

Транзакції в PostgreSQL забезпечують ізоляцію — одну з ключових характеристик ACID. Для цього система використовує блокування. Блокування — це механізм, який гарантує, що кілька транзакцій не "воюють" за один і той самий запис або таблицю. Уяви собі чергу в супермаркеті: на касі обслуговується лише одна людина за раз. Блокування працюють приблизно так само, тільки вони керують доступом до даних у таблицях.

Основні види блокувань

У PostgreSQL є кілька типів блокувань, деякі з яких ти, можливо, вже бачив у EXPLAIN ANALYZE:

  1. ROW EXCLUSIVE (рядкова ексклюзивна блокування) — виникає при зміні даних у рядку. Це найчастіше використовуваний тип блокування, наприклад, при виконанні INSERT, UPDATE або DELETE.
  2. SHARE (загальна блокування) — використовується для операцій, які гарантують, що дані не зміняться, поки виконується запит, наприклад, при виконанні SELECT ... FOR SHARE.
  3. EXCLUSIVE (ексклюзивна блокування) — запобігає будь-яким іншим операціям з даними, окрім читання.

Можна сказати, що блокування — це такі собаки-сторожі, які охороняють наші дані.

Проблема блокувань: що може піти не так?

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

  1. Транзакція 1 блокує рядок A і хоче отримати доступ до рядка B.
  2. Транзакція 2 блокує рядок B і хоче отримати доступ до рядка A.
  3. Оскільки обидва рядки заблоковані, жодна з транзакцій не може завершитись.

Приклад взаємного блокування

Щоб відчути біль програміста, який зіткнувся з deadlock, уяви такий сценарій:

-- Транзакція 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- Одночасно
-- Транзакція 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;

-- Транзакція 1 намагається заблокувати id = 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;

-- Транзакція 2 намагається заблокувати id = 1
UPDATE accounts SET balance = balance + 200 WHERE id = 1;

Вуаля! Кожна транзакція заблокована іншою. PostgreSQL рано чи пізно це виявляє і викидає помилку взаємного блокування.

Як уникнути взаємних блокувань?

  1. Послідовний порядок доступу до даних. Намагайся завжди звертатись до даних в одному й тому ж порядку. Якщо транзакція 1 спочатку блокує рядок A, а потім рядок B, то транзакція 2 повинна дотримуватись тієї ж логіки: спочатку рядок A, потім рядок B.
-- Правильний порядок доступу
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
  1. Мінімізуй тривалість транзакцій. Довгі транзакції збільшують ймовірність deadlock. Завершуй транзакції якнайшвидше: BEGIN, виконуй всі потрібні зміни і одразу COMMIT.

  2. Використовуй рівні ізоляції транзакцій. Якщо твоя задача не вимагає суворої ізоляції даних, розглянь можливість використання рівня ізоляції READ COMMITTED. Він дозволяє уникнути деяких блокувань.

Тайм-аути та діагностика блокувань

Існують інструменти для діагностики та запобігання блокуванням у PostgreSQL. Наприклад, можна встановити максимальний час очікування блокування:

SET lock_timeout = '5s';  -- Встановлюємо таймаут блокування на 5 секунд

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

Відстеження блокувань у PostgreSQL

Одна з корисних команд для моніторингу блокувань — це pg_locks. Вона показує всі активні блокування в системі:

SELECT * FROM pg_locks;

Ти можеш побачити, які транзакції утримують блокування і які чекають їх зняття. Особливо корисно при відладці deadlock.

Особливості LOCK та ручне блокування об'єктів

Якщо тобі потрібно вручну керувати блокуваннями, використовуй команду LOCK:

LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;

Увага: ACCESS EXCLUSIVE — найсильніше блокування, воно забороняє будь-які інші операції з таблицею, навіть SELECT. Використовуй його лише для особливих випадків (наприклад, зміна структури таблиці).

Для блокування окремих рядків при зміні використовуй SELECT ... FOR UPDATE:

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

Це гарантує, що інші транзакції не зможуть змінити ці рядки до кінця твоєї транзакції.

Що варто пам'ятати про блокування?

Блокування — це не зло, а інструмент. Вони допомагають зберігати цілісність даних, але вимагають уважного ставлення. Ось кілька ключових порад:

  1. Не починай транзакцію, якщо точно не знаєш, навіщо вона тобі.
  2. Завершуй транзакцію якнайшвидше.
  3. Уникай доступу до даних у різному порядку.
  4. Використовуй рівні ізоляції, які підходять для твоєї задачі.

Тепер ти готовий працювати з блокуваннями та deadlock! Хай буде з тобою pg_locks і мудрість ACID.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ