Pessimistic Offline Lock (Пессимистичная блокировка)
Паттерн проектирования Pessimistic Offline Lock
Описание Pessimistic Offline Lock
Предотвращает конфликты между конкурирующими бизнес-транзакциями, разрешая доступ к одинм данным только одной бизнес-транзакции за раз.
Так как локальная конкуренция включает в себя работу с данными бизнес-транзакции, которая содержит несколько запросов, то наиболее простой подход был бы держать транзакцию открытой на протяжении всей бизнес-транзакции. Но, к сожалению, этот подход не всегда работает хорошо, потому что системы управления транзакциями не заточены под работу с длинными транзацкиями. По этому приходится делать несколько системных транзакций, а остальную конкуренцию отслеживать самостоятельно.
Первый из подходов, который следует попробовать в этом случае - Optimistic Offline Lock. Однако, этот паттерн имеет свои недостатки. Например, если несколько людей пытаются получить доступ к одним данным в одной бизнес-транзакции, один из них легко сможет сделать commit, но остальные получат конфликт и не смогут продолжить работу. Из-за того, что конфликты определяются только в конце бизнес-транзакции (а не предотвращаются), жертвы будут делать все действия тразакции и в последний момент обнаружат, что вся их работа псу под хвост. Если так будет происходить, системы с длинными транзакциями вскоре станут непопулярны.
Pessimistic Offline Lock предотвращает конфликты, исключая их вообще. Этот паттерн заставляет бизнес-транзакцию накладывать блокировку на ту часть данных, с которыми она работает, так что чтобы начать работу с данными другой пользователь сначала ждёт, пока данные освободятся.
Реализация в популярных СУБД
PostgreSQL
PostgreSQL предоставляет несколько типов блокировок для реализации пессимистичной блокировки:
- SELECT FOR UPDATE - блокирует строки для обновления
- SELECT FOR SHARE - блокирует строки для чтения
- SELECT FOR NO KEY UPDATE - блокирует строки, но не ключи
- SELECT FOR KEY SHARE - блокирует только ключи
-- Блокировка строки для обновления
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 поддерживает различные уровни блокировок:
- Shared Lock (S) - блокировка на чтение
- Exclusive Lock (X) - блокировка на запись
- Intention Locks - намерения блокировки
- Gap Locks - блокировки промежутков
-- Блокировка строки для обновления
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 предоставляет мощные механизмы блокировок:
- Row-Level Locking - блокировка на уровне строк
- Table-Level Locking - блокировка на уровне таблиц
- Deadlock Detection - автоматическое обнаружение взаимных блокировок
-- Блокировка строки для обновления
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 использует различные типы блокировок:
- Shared (S) - разделяемая блокировка
- Exclusive (X) - исключительная блокировка
- Update (U) - блокировка обновления
- Intent Locks - намерения блокировки
-- Блокировка строки для обновления
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 использует файловые блокировки:
- SHARED - разделяемая блокировка
- RESERVED - зарезервированная блокировка
- PENDING - ожидающая блокировка
- EXCLUSIVE - исключительная блокировка
-- Блокировка строки для обновления
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 | Средняя | Ограниченная |
Рекомендации по использованию
- PostgreSQL - лучший выбор для веб-приложений с высокой конкурентностью
- MySQL - хороший баланс производительности и простоты
- Oracle - для корпоративных приложений с высокими требованиями
- SQL Server - для Windows-среды и .NET приложений
- SQLite - для встраиваемых приложений и прототипирования
Использована иллюстрация с сайта Мартина Фаулера.