Две транзакции в базе выполняют перевод средств: T1 читает баланс A, вычитает сумму, обновляет; T2 аналогично для B — опишите сценарии «lost update», «dirty read», «nonrepeatable read» и «phantom read» на примере, укажите уровень изоляции SQL, при котором исчезнут эти аномалии, и объясните компромиссы по производительности
Кратко и по сути — для каждого явления: сценарий (шаги), почему оно происходит, при каком уровне изоляции исчезает и компромисс по производительности. 1) Lost update - Сценарий (две транзакции модифицируют один и тот же счёт): T1:T1:T1: читает A=100A=100A=100 → вычитает 303030 → готовит UPDATE A=70A=70A=70. T2:T2:T2: одновременно читает A=100A=100A=100 → вычитает 202020 → выполняет UPDATE A=80A=80A=80. Если T2T2T2 выполнит COMMIT позже, итоговая сумма может стать 808080 и обновление T1T1T1 «потеряется». - Устраняется при: SERIALIZABLE или при использовании явных блокировок/контроля конкурентности (например, SELECT ... FOR UPDATE или optimistic locking с проверкой версии). - Компромисс: блокировки/версионирование снижают параллелизм — возможны задержки и дедлоки; optimistic locking требует дополнительных проверок и откатов при конфликте. 2) Dirty read - Сценарий: T1T1T1 обновляет, но не коммитит; T2T2T2 читает это незакоммиченное значение: T1:T1:T1: UPDATE A:=50A:=50A:=50 (но ещё без COMMIT). T2:T2:T2: SELECT AAA → получает 505050. Если T1T1T1 откатит, T2T2T2 оперировал «грязными» данными. - Устраняется при: READ COMMITTED и выше. - Компромисс: READ COMMITTED требует синхронизации на запись/версионность, чуть хуже параллелизм, но обычно минимальное влияние на производительность по сравнению с более строгими уровнями. 3) Non-repeatable read - Сценарий: T1T1T1 читает AAA, затем T2T2T2 изменяет и коммитит, T1T1T1 снова читает — видит другое значение: T1:T1:T1: SELECT AAA → 100100100. T2:T2:T2: UPDATE A:=80A:=80A:=80 → COMMIT. T1:T1:T1: повторный SELECT AAA → 808080. - Устраняется при: REPEATABLE READ (или SERIALIZABLE). - Компромисс: REPEATABLE READ удерживает снапшоты или блокировки для стабильных чтений — выше издержки на память/логи и меньше параллелизма, но чтения становятся детерминированными внутри транзакции. 4) Phantom read - Сценарий (запрос множества строк): T1:T1:T1: SELECT COUNT(*) FROM accounts WHERE balance > 0 → получен результат 101010. T2:T2:T2: INSERT INTO accounts (balance) VALUES (50) → COMMIT. T1:T1:T1: повторный SELECT COUNT(*) → 111111 (появился «фантом»). - Устраняется при: SERIALIZABLE (стандартно). В некоторых СУБД REPEATABLE READ с расширенными механизмами (predicate locking / MVCC с gap-locks) тоже предотвращает фантомы, но это СУБД‑зависимо. - Компромисс: SERIALIZABLE даёт наибольшую гарантию корректности, но существенно увеличивает конкуренцию за ресурсы, количество блокировок/конфликтов и снижает пропускную способность; может потребоваться больше откатов и повторных попыток транзакций. Резюме по уровням и явлениям (упрощённо): - READ UNCOMMITTED — все аномалии возможны. - READ COMMITTED — предотвращает dirty read; допускает non-repeatable и phantom (и может допускать lost update). - REPEATABLE READ — предотвращает dirty и non-repeatable read; phantom — зависит от реализации. - SERIALIZABLE — предотвращает все перечисленные аномалии (и lost update при стандартной сериализации). Практический совет: для честных финансовых операций лучше либо SERIALIZABLE либо READ COMMITTED + явные блокировки/оптимистическая проверка версии для операций «читать→изменить→записать», чтобы избежать lost update при приемлемом уровне параллелизма.
1) Lost update
- Сценарий (две транзакции модифицируют один и тот же счёт):
T1:T1:T1: читает A=100A=100A=100 → вычитает 303030 → готовит UPDATE A=70A=70A=70.
T2:T2:T2: одновременно читает A=100A=100A=100 → вычитает 202020 → выполняет UPDATE A=80A=80A=80.
Если T2T2T2 выполнит COMMIT позже, итоговая сумма может стать 808080 и обновление T1T1T1 «потеряется».
- Устраняется при: SERIALIZABLE или при использовании явных блокировок/контроля конкурентности (например, SELECT ... FOR UPDATE или optimistic locking с проверкой версии).
- Компромисс: блокировки/версионирование снижают параллелизм — возможны задержки и дедлоки; optimistic locking требует дополнительных проверок и откатов при конфликте.
2) Dirty read
- Сценарий: T1T1T1 обновляет, но не коммитит; T2T2T2 читает это незакоммиченное значение:
T1:T1:T1: UPDATE A:=50A:=50A:=50 (но ещё без COMMIT).
T2:T2:T2: SELECT AAA → получает 505050.
Если T1T1T1 откатит, T2T2T2 оперировал «грязными» данными.
- Устраняется при: READ COMMITTED и выше.
- Компромисс: READ COMMITTED требует синхронизации на запись/версионность, чуть хуже параллелизм, но обычно минимальное влияние на производительность по сравнению с более строгими уровнями.
3) Non-repeatable read
- Сценарий: T1T1T1 читает AAA, затем T2T2T2 изменяет и коммитит, T1T1T1 снова читает — видит другое значение:
T1:T1:T1: SELECT AAA → 100100100.
T2:T2:T2: UPDATE A:=80A:=80A:=80 → COMMIT.
T1:T1:T1: повторный SELECT AAA → 808080.
- Устраняется при: REPEATABLE READ (или SERIALIZABLE).
- Компромисс: REPEATABLE READ удерживает снапшоты или блокировки для стабильных чтений — выше издержки на память/логи и меньше параллелизма, но чтения становятся детерминированными внутри транзакции.
4) Phantom read
- Сценарий (запрос множества строк):
T1:T1:T1: SELECT COUNT(*) FROM accounts WHERE balance > 0 → получен результат 101010.
T2:T2:T2: INSERT INTO accounts (balance) VALUES (50) → COMMIT.
T1:T1:T1: повторный SELECT COUNT(*) → 111111 (появился «фантом»).
- Устраняется при: SERIALIZABLE (стандартно). В некоторых СУБД REPEATABLE READ с расширенными механизмами (predicate locking / MVCC с gap-locks) тоже предотвращает фантомы, но это СУБД‑зависимо.
- Компромисс: SERIALIZABLE даёт наибольшую гарантию корректности, но существенно увеличивает конкуренцию за ресурсы, количество блокировок/конфликтов и снижает пропускную способность; может потребоваться больше откатов и повторных попыток транзакций.
Резюме по уровням и явлениям (упрощённо):
- READ UNCOMMITTED — все аномалии возможны.
- READ COMMITTED — предотвращает dirty read; допускает non-repeatable и phantom (и может допускать lost update).
- REPEATABLE READ — предотвращает dirty и non-repeatable read; phantom — зависит от реализации.
- SERIALIZABLE — предотвращает все перечисленные аномалии (и lost update при стандартной сериализации).
Практический совет: для честных финансовых операций лучше либо SERIALIZABLE либо READ COMMITTED + явные блокировки/оптимистическая проверка версии для операций «читать→изменить→записать», чтобы избежать lost update при приемлемом уровне параллелизма.