Ответ
Конкуренция транзакций — это ситуация, когда несколько параллельных транзакций пытаются получить доступ (чтение или запись) к одним и тем же данным в базе данных, что может привести к проблемам согласованности.
Основные проблемы (аномалии)
- Потерянное обновление (Lost Update): Две транзакции читают одну запись, обе изменяют её, но сохраняется только результат последнего коммита, перезаписывая изменения первой.
- Грязное чтение (Dirty Read): Транзакция читает незафиксированные (и потенциально откатываемые) данные другой транзакции.
- Неповторяющееся чтение (Non-repeatable Read): В рамках одной транзакции повторное чтение одной и той же записи даёт разные результаты, потому что другая транзакция изменила и зафиксировала её.
- Фантомное чтение (Phantom Read): Повторное выполнение запроса с условием возвращает разный набор строк из-за того, что другая транзакция добавила или удалила записи, удовлетворяющие условию.
Пример (Lost Update)
// Транзакция 1
entityManager.getTransaction().begin();
Product p1 = entityManager.find(Product.class, 1L); // Читает price = 100
// Транзакция 2 в это время:
// Product p2 = entityManager.find(Product.class, 1L);
// p2.setPrice(p2.getPrice() + 50); // price = 150
// entityManager.getTransaction().commit();
p1.setPrice(p1.getPrice() + 100); // Ожидает price = 100, устанавливает 200
entityManager.getTransaction().commit(); // Перезаписывает цену 150 на 200
Решения
- Уровни изоляции транзакций (от низкого к высокому):
READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ,SERIALIZABLE. Каждый следующий уровень предотвращает определённый набор аномалий, но может снижать производительность. - Механизмы блокировок:
- Пессимистичные: Данные блокируются на время транзакции (
SELECT ... FOR UPDATE). - Оптимистичные: Используется контроль версий (например, поле
version). При коммите проверяется, не изменились ли данные с момента чтения, в случае конфликта транзакция откатывается.
- Пессимистичные: Данные блокируются на время транзакции (
Ответ 18+ 🔞
Давай разберём эту тему, а то я вижу, у тебя глаза уже стекленеют от этих умных слов. Конкуренция транзакций — это когда несколько твоих запросов в базу начинают драться за одну и ту же запись, как два мужика за последнюю бутылку в ларьке после зарплаты. Итог предсказуем: пиздец и разбитые надежды.
Чего может пойти не по плану (или классика жанра «Я ж думал, всё норм»)
-
Потерянное обновление (Lost Update). Представь: ты и твой кореш смотрите на цену товара в базе. Видите «100 рублей». Ты такой: «О, надо накинуть сотку!». А он: «Надо накинуть полтинник!». Ты быстренько меняешь на 200 и сохраняешь. А он, чуть позже, меняет на 150 и тоже сохраняет. И что в итоге? Твоя сотка, блядь, испарилась в небытие! В базе лежит 150. Вот тебе и потерянное обновление — один как будто и не работал. Пиздец обидно.
-
Грязное чтение (Dirty Read). Это когда ты, такой довольный, читаешь данные, которые другая транзакция ещё даже не сохранила толком. А она возьми да откатись! А ты уже на эти «грязные» данные заложился, построил логику, и теперь сидишь с ебалом, полным недоумения. Читал-то хуйню, которая в итоге не состоялась.
-
Неповторяющееся чтение (Non-repeatable Read). Вообще анекдот. В рамках одной своей же транзакции ты дважды читаешь одну запись. А между этими чтениями какая-то тварь незаметно её изменила и закоммитила. И ты такой: «Стоп, а я вроде только что видел тут другое значение... Ёпта, я что, уже глючу?». Нет, просто данные под твоим носом поменялись.
-
Фантомное чтение (Phantom Read). Ещё круче. Ты делаешь выборку, например, «дай мне всех пользователей из Москвы». Получаешь 10 штук. Проходит миллисекунда, ты делаешь ТОЧНО ТАКОЙ ЖЕ запрос, а тебе уже 11 или 9! Как будто фантом какой-то строки то появляется, то исчезает. На самом деле, просто параллельная транзакция кого-то добавила или удалила. Волнение ебать, не правда ли?
Живой пример, как всё просрать (тот самый Lost Update)
Смотри, как это выглядит в коде, если ничего не предусмотреть:
// Транзакция 1 (Это ты, решивший поднять цену на 100)
entityManager.getTransaction().begin();
Product p1 = entityManager.find(Product.class, 1L); // Читает price = 100
// А в этот самый момент, блядь, вклинивается Транзакция 2 (твой «кореш»):
// Product p2 = entityManager.find(Product.class, 1L);
// p2.setPrice(p2.getPrice() + 50); // price = 150
// entityManager.getTransaction().commit(); // Успел раньше, сука!
p1.setPrice(p1.getPrice() + 100); // Ты-то думаешь, что price ещё 100, и ставишь 200
entityManager.getTransaction().commit(); // И БАМ! Перезаписываешь своей двойкой его 150. Его полтинник — в тартарары.
Красота, да? Оба думали, что работают, а по факту труд одного пошёл нахуй.
Как со всем этим бороться, чтобы не сойти с ума
-
Уровни изоляции транзакций. Это как степени паранойи в системе. От «да похуй, читай что хочешь» (
READ_UNCOMMITTED) до «все встали в строй и выполняются строго по очереди, блядь!» (SERIALIZABLE). Чем выше уровень, тем меньше аномалий, но тем всё медленнее и печальнее. Выбирай, что тебе дороже: скорость или целостность данных. ОбычноREAD_COMMITTED— золотая середина, грязные чтения отсекает. -
Механизмы блокировок.
- Пессимистичные. Это когда ты с порога заявляешь: «Эта запись — моя, все нахуй!». Вешаешь на неё замок (
SELECT ... FOR UPDATE) и спокойно работаешь, пока остальные ждут. Надёжно, но если все начнут так делать, можно в дедлоке зависнуть. - Оптимистичные. А это по-русски «авось пронесёт». Ты работаешь с данными, а система тихонько следит за их версией (через поле
version). Когда ты идёшь сохранять, она проверяет: «А не пиздит ли он? Не изменил ли кто данные пока он тут умничал?». Если изменил — тебе вежливо показывают ошибку: «Извини, чувак, но пока ты думал, мир изменился. Начни заново». Менее затратно, но требует обработки этих самых конфликтов.
- Пессимистичные. Это когда ты с порога заявляешь: «Эта запись — моя, все нахуй!». Вешаешь на неё замок (
Вот и вся магия. Главное — понимать, что в многопользовательской среде база данных это не тихий омут, а ебаный цирк с конями, и готовиться к этому соответственно.