Транзакції в PostgreSQL забезпечують ізоляцію — одну з ключових характеристик ACID. Для цього система використовує блокування. Блокування — це механізм, який гарантує, що кілька транзакцій не "воюють" за один і той самий запис або таблицю. Уяви собі чергу в супермаркеті: на касі обслуговується лише одна людина за раз. Блокування працюють приблизно так само, тільки вони керують доступом до даних у таблицях.
Основні види блокувань
У PostgreSQL є кілька типів блокувань, деякі з яких ти, можливо, вже бачив у EXPLAIN ANALYZE:
ROW EXCLUSIVE(рядкова ексклюзивна блокування) — виникає при зміні даних у рядку. Це найчастіше використовуваний тип блокування, наприклад, при виконанніINSERT,UPDATEабоDELETE.SHARE(загальна блокування) — використовується для операцій, які гарантують, що дані не зміняться, поки виконується запит, наприклад, при виконанніSELECT ... FOR SHARE.EXCLUSIVE(ексклюзивна блокування) — запобігає будь-яким іншим операціям з даними, окрім читання.
Можна сказати, що блокування — це такі собаки-сторожі, які охороняють наші дані.
Проблема блокувань: що може піти не так?
Тепер, коли ми розуміємо, що таке блокування, давай подивимось, які проблеми вони можуть викликати. Найбільша з них — це проблема взаємного блокування. Взаємне блокування (або deadlock) — це ситуація, коли дві (або більше) транзакції чекають одна на одну, через що виконання застрягає в нескінченному циклі. Типово це виглядає так:
- Транзакція 1 блокує рядок A і хоче отримати доступ до рядка B.
- Транзакція 2 блокує рядок B і хоче отримати доступ до рядка A.
- Оскільки обидва рядки заблоковані, жодна з транзакцій не може завершитись.
Приклад взаємного блокування
Щоб відчути біль програміста, який зіткнувся з 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 спочатку блокує рядок 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;
Мінімізуй тривалість транзакцій. Довгі транзакції збільшують ймовірність deadlock. Завершуй транзакції якнайшвидше:
BEGIN, виконуй всі потрібні зміни і одразуCOMMIT.Використовуй рівні ізоляції транзакцій. Якщо твоя задача не вимагає суворої ізоляції даних, розглянь можливість використання рівня ізоляції
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;
Це гарантує, що інші транзакції не зможуть змінити ці рядки до кінця твоєї транзакції.
Що варто пам'ятати про блокування?
Блокування — це не зло, а інструмент. Вони допомагають зберігати цілісність даних, але вимагають уважного ставлення. Ось кілька ключових порад:
- Не починай транзакцію, якщо точно не знаєш, навіщо вона тобі.
- Завершуй транзакцію якнайшвидше.
- Уникай доступу до даних у різному порядку.
- Використовуй рівні ізоляції, які підходять для твоєї задачі.
Тепер ти готовий працювати з блокуваннями та deadlock! Хай буде з тобою pg_locks і мудрість ACID.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ