Pessimistic Offline Lock (Пессимистичная блокировка)

Паттерн проектирования Pessimistic Offline Lock

Паттерн проектирования Pessimistic Offline Lock

Описание Pessimistic Offline Lock

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

Так как локальная конкуренция включает в себя работу с данными бизнес-транзакции, которая содержит несколько запросов, то наиболее простой подход был бы держать транзакцию открытой на протяжении всей бизнес-транзакции. Но, к сожалению, этот подход не всегда работает хорошо, потому что системы управления транзакциями не заточены под работу с длинными транзацкиями. По этому приходится делать несколько системных транзакций, а остальную конкуренцию отслеживать самостоятельно.

Первый из подходов, который следует попробовать в этом случае - Optimistic Offline Lock. Однако, этот паттерн имеет свои недостатки. Например, если несколько людей пытаются получить доступ к одним данным в одной бизнес-транзакции, один из них легко сможет сделать commit, но остальные получат конфликт и не смогут продолжить работу. Из-за того, что конфликты определяются только в конце бизнес-транзакции (а не предотвращаются), жертвы будут делать все действия тразакции и в последний момент обнаружат, что вся их работа псу под хвост. Если так будет происходить, системы с длинными транзакциями вскоре станут непопулярны.

Pessimistic Offline Lock предотвращает конфликты, исключая их вообще. Этот паттерн заставляет бизнес-транзакцию накладывать блокировку на ту часть данных, с которыми она работает, так что чтобы начать работу с данными другой пользователь сначала ждёт, пока данные освободятся.

Реализация в популярных СУБД

PostgreSQL

PostgreSQL предоставляет несколько типов блокировок для реализации пессимистичной блокировки:

-- Блокировка строки для обновления
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- Работа с данными...
UPDATE users SET name = 'New Name' WHERE id = 1;
COMMIT;

-- Блокировка с таймаутом
SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT;

-- Блокировка с ожиданием
SELECT * FROM users WHERE id = 1 FOR UPDATE SKIP LOCKED;

MySQL (InnoDB)

MySQL InnoDB поддерживает различные уровни блокировок:

-- Блокировка строки для обновления
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- Работа с данными...
UPDATE users SET name = 'New Name' WHERE id = 1;
COMMIT;

-- Блокировка с таймаутом
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- Если блокировка не получена в течение innodb_lock_wait_timeout, 
-- транзакция откатывается

Oracle Database

Oracle предоставляет мощные механизмы блокировок:

-- Блокировка строки для обновления
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- Работа с данными...
UPDATE users SET name = 'New Name' WHERE id = 1;
COMMIT;

-- Блокировка с NOWAIT
SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT;

-- Блокировка с таймаутом
SELECT * FROM users WHERE id = 1 FOR UPDATE WAIT 10;

SQL Server

SQL Server использует различные типы блокировок:

-- Блокировка строки для обновления
BEGIN TRANSACTION;
SELECT * FROM users WITH (UPDLOCK) WHERE id = 1;
-- Работа с данными...
UPDATE users SET name = 'New Name' WHERE id = 1;
COMMIT;

-- Блокировка с таймаутом
SELECT * FROM users WITH (UPDLOCK, READPAST) WHERE id = 1;

SQLite

SQLite использует файловые блокировки:

-- Блокировка строки для обновления
BEGIN IMMEDIATE;
SELECT * FROM users WHERE id = 1;
-- Работа с данными...
UPDATE users SET name = 'New Name' WHERE id = 1;
COMMIT;

Сравнение производительности

СУБД Тип блокировок Производительность Масштабируемость
PostgreSQL MVCC + Row-level Высокая Отличная
MySQL InnoDB MVCC + Row-level Высокая Хорошая
Oracle MVCC + Row-level Очень высокая Отличная
SQL Server Row-level + Page-level Высокая Хорошая
SQLite File-level Средняя Ограниченная

Рекомендации по использованию

Использована иллюстрация с сайта Мартина Фаулера.

Источник