Ответ
Оптимистичная блокировка (Optimistic Concurrency Control) — это стратегия для работы с параллельным доступом к данным в БД, которая предполагает, что конфликты при одновременном изменении одной записи маловероятны. Вместо явной блокировки записи на время транзакции, конфликт обнаруживается в момент фиксации изменений и должен быть обработан приложением.
Принцип работы:
- Каждая запись в таблице имеет поле-версию (обычно
versionилиupdated_at). - Клиент, читая запись, также получает текущее значение версии.
- При обновлении запись модифицируется только если её версия в БД совпадает с той, что была получена клиентом. Версия увеличивается.
- Если версии не совпали (запись уже обновил кто-то другой), операция обновления не затрагивает ни одной строки, и приложение получает сигнал о конфликте.
Пример реализации в Node.js с PostgreSQL и node-postgres:
async function updateProduct(productId, newPrice, clientVersion) {
const query = `
UPDATE products
SET price = $1, version = version + 1, updated_at = NOW()
WHERE id = $2 AND version = $3
RETURNING id, price, version;
`;
const values = [newPrice, productId, clientVersion];
const result = await pool.query(query, values);
if (result.rowCount === 0) {
// Конфликт версий: запись не найдена с указанной версией
// Здесь можно либо выбросить ошибку, либо реализовать повторную попытку
const currentRecord = await pool.query('SELECT * FROM products WHERE id = $1', [productId]);
throw new Error(`Конфликт оптимистичной блокировки. Текущая версия: ${currentRecord.rows[0].version}. Ваша версия: ${clientVersion}.`);
}
return result.rows[0]; // Возвращаем обновленную запись
}
// Использование
// 1. Сначала читаем продукт: const product = await pool.query('SELECT id, price, version FROM products WHERE id=123');
// 2. Пытаемся обновить: try { await updateProduct(123, 99.99, product.rows[0].version); } catch(err) { /* Обработка конфликта */ }
Плюсы:
- Высокая производительность, так как нет долгих блокировок, ожидающих завершения транзакций.
- Хорошо подходит для сценариев с высокой частотой чтения и низкой частотой конфликтующих записей (например, редактирование профиля пользователя).
Минусы:
- Требует дополнительного поля в таблице.
- Приложение должно быть готово обрабатывать ошибки конфликтов (например, предлагать пользователю обновить данные и повторить действие).
- Не подходит для сценариев, где конфликты записи происходят очень часто.
В экосистеме Node.js эта стратегия часто реализуется на уровне ORM (например, в Sequelize есть опция version).
Ответ 18+ 🔞
А, оптимистичная блокировка! Ну это ж классика, ёпта. Представь себе ситуацию: два чувака одновременно лезут редактировать одну и ту же запись в базе. Пессимист сразу навесит замок, чтоб никто не трогал, пока он сам не закончит. А оптимист — он же по жизни верит в лучшее. Он думает: «Да похуй, кто ж будет со мной конфликтовать? Я быстро!». И лезет менять данные без всяких блокировок. Ну а если всё-таки упёрлись лбами — вот тогда уже разбираемся, по факту. Хитрая жопа, но работает.
Как это, блядь, устроено:
- К каждой строчке в таблице прикручивают поле-версию. Обычно это
version(просто цифра) илиupdated_at(время последнего обновления). Это как тату «не трожь, я обновлён». - Когда ты читаешь запись, ты заодно хватаешь и эту версию. Запоминаешь, типа «я видел её вот такой».
- А когда хочешь её обновить, то пишешь в базу что-то вроде: «Эй, PostgreSQL, обнови эту запись, но ТОЛЬКО если её версия до сих пор та самая, которую я видел. И заодно версию увеличь, чтоб следующий лох понял, что тут уже поработали».
- Если за то время, пока ты думал, запись уже успел поменять какой-нибудь другой полупидор, то версии не совпадут. Твоя команда
UPDATEнихрена не обновит (ноль строк затронет), и ты получишь сигнал: «Братан, опоздал, тут уже всё поменялось».
Вот как это выглядит в коде на Node.js с PostgreSQL:
async function updateProduct(productId, newPrice, clientVersion) {
const query = `
UPDATE products
SET price = $1, version = version + 1, updated_at = NOW()
WHERE id = $2 AND version = $3
RETURNING id, price, version;
`;
const values = [newPrice, productId, clientVersion];
const result = await pool.query(query, values);
if (result.rowCount === 0) {
// Вот он, пиздец, конфликт! Запись с той версией, которую ты помнишь, уже испарилась.
// Тут надо либо орать ошибку, либо пытаться ещё раз, как упоротый.
const currentRecord = await pool.query('SELECT * FROM products WHERE id = $1', [productId]);
throw new Error(`Конфликт оптимистичной блокировки. Текущая версия: ${currentRecord.rows[0].version}. Ваша версия: ${clientVersion}.`);
}
return result.rows[0]; // Возвращаем обновлённую запись, всё чики-пуки.
}
// Как этим пользоваться:
// 1. Сначала читаешь продукт: const product = await pool.query('SELECT id, price, version FROM products WHERE id=123');
// 2. Потом пытаешься обновить: try { await updateProduct(123, 99.99, product.rows[0].version); } catch(err) { /* Лови конфликт и решай, что делать */ }
Плюсы, блядь:
- Скорость огонь. Никаких долгих блокировок, которые всё тормозят. Все работают параллельно, как тараканы.
- Идеально для ситуаций, где все в основном читают, а пишут редко и в разные места. Типа, два пользователя редко когда будут одновременно менять один и тот же товар в корзине. Хотя... бывает всякое, ядрёна вошь.
Минусы, сука:
- Надо таскать с собой это поле версии в каждой таблице. Лишний геморрой.
- Приложение должно быть не тупое и уметь ловить эти конфликты. Не просто «ой, ошибка», а нормально обработать: показать пользователю актуальные данные и сказать «смотри, всё поменялось, давай заново».
- Если у тебя какая-то запись — как горячая пирожок, её двадцать человек в секунду пытаются обновить, то тут оптимистичная блокировка превратится в ад. Все будут постоянно получать конфликты и перезапускать операции. Тут уже надо думать про другие подходы.
В мире Node.js эту тему часто делают за тебя всякие ORM, типа Sequelize. Там просто галочку поставь version: true, и они сами под капотом эту магию версий провернут. Удобно, конечно, но понимать, как оно работает изнутри — всё равно полезно. А то потом будешь охуевать, почему данные не обновились.