87 lines
4.3 KiB
PL/PgSQL
87 lines
4.3 KiB
PL/PgSQL
- Задание 1: Разбери ситуацию
|
||
|
||
У тебя две транзакции работают с таблицей `accounts`:
|
||
|
||
**Транзакция A:**
|
||
|
||
BEGIN;
|
||
SELECT balance FROM accounts WHERE id = 1; -- balance = 100
|
||
-- ждёт 10 секунд
|
||
SELECT balance FROM accounts WHERE id = 1; -- balance = ???
|
||
COMMIT;
|
||
|
||
**Транзакция B (выполняется параллельно):**
|
||
|
||
BEGIN;
|
||
UPDATE accounts SET balance = 200 WHERE id = 1;
|
||
COMMIT;
|
||
|
||
**Вопросы:**
|
||
|
||
1. Какой баланс увидит транзакция A во втором SELECT на уровне **Read Committed**?
|
||
УВИДИТ 200
|
||
2. Какой баланс увидит транзакция A во втором SELECT на уровне **Repeatable Read**?
|
||
УВИДИТ 100
|
||
3. Объясни, почему результаты разные.
|
||
ПОТОМУ ЧТО УРОВНИ ИЗОЛЯЦИИ РАЗНЫЕ, В ПЕРВОМ СЛУЧАЕ ВИДИМ ТОЛЬКО ЗАКОММИЧЕННЫЕ ДАННЫЕ, А ВО ВТОРОМ ТЕ ДАННЫЕ, КОТОРЫЕ БЫЛИ В САМОМ НАЧАЛЕ ТРАЗАКЦИИ.
|
||
|
||
- Задание 2: Практика
|
||
|
||
Создай таблицу:
|
||
|
||
CREATE TABLE test_isolation (id INT PRIMARY KEY, value INT);
|
||
INSERT INTO test_isolation VALUES (1, 100);
|
||
|
||
Открой **две** вкладки в DBeaver.
|
||
|
||
**В первой вкладке:**
|
||
|
||
BEGIN;
|
||
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
|
||
SELECT value FROM test_isolation WHERE id = 1;
|
||
|
||
**Во второй вкладке:**
|
||
|
||
BEGIN;
|
||
UPDATE test_isolation SET value = 200 WHERE id = 1;
|
||
COMMIT;
|
||
|
||
**Обратно в первой вкладке:**
|
||
|
||
SELECT value FROM test_isolation WHERE id = 1;
|
||
COMMIT;
|
||
|
||
**Вопрос:** Какое значение ты увидел во втором SELECT? Почему?
|
||
УВИДЕЛ 100, ПОТОМУ ЧТО dbeaver В ПЕРВОЙ ТРАНЗАКЦИИ ПОДДЕРЖИВАЕТ Repeatable Read И ТРАНЗАКЦИЯ НЕ БЫЛА ЗАКОММИЧЕНА.
|
||
|
||
Повтори эксперимент с уровнем **Read Committed** вместо **Repeatable Read**. Что изменилось?
|
||
В ДАННОМ СЛУЧАЕ ИЗМЕНИЛСЯ ТИП ИЗОЛЯЦИИ И ТК ВТОРАЯ ТРАЗАКЦИЯ БЫЛА ЗАКОМИЧЕНА, ТО ВЫВОД ТРЕТЬЕГО ЗАПРОСА ПОКАЗАЛ ИМЕННО ПОСЛЕДНЕЕ ЗАКОМИЧЕННОЕ.
|
||
|
||
- Задание 3: Найди ошибку
|
||
|
||
Вася написал код для списания денег со счёта:
|
||
|
||
BEGIN;
|
||
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||
|
||
-- Проверяем баланс
|
||
SELECT balance FROM accounts WHERE id = 1; -- balance = 100
|
||
|
||
-- Думаем 5 секунд (пользователь вводит сумму)
|
||
|
||
-- Списываем деньги
|
||
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
|
||
|
||
COMMIT;
|
||
|
||
**Проблема:** Пока Вася думал 5 секунд, другая транзакция списала 80 руб. Теперь баланс стал отрицательным
|
||
|
||
**Вопросы:**
|
||
|
||
1. Почему так произошло?
|
||
ПОТОМУ ЧТО ВНУТРЬ ТРАНЗАКЦИИ ВОРВОЛАСЬ ДРУГАЯ, А UPDATE БЕРЕТ ЗНАЧЕНИЕ НА ДАННЫЙ МОМЕНТ. ЧТОБЫ ЭТОГО ИЗБЕЖАТЬ ВАСЕ НАДО БЫЛО В СВОЕЙ ТРАНЗАКЦИИ НАПИСТАЬЬ FOR UPDATE И ЕГО ЗНАЧЕНИЕ БЫ ЗАМОРОЗИЛОСЬ.
|
||
|
||
2. Какой уровень изоляции поможет избежать этой проблемы?
|
||
Serializable
|
||
3. Как ещё можно решить эту проблему без смены уровня изоляции? (подсказка: проверяй баланс перед UPDATE)
|
||
КАК ВЫШЕ СКАЗАЛ МОЖНО ИСПОЛЬЗОВАТЬ FOR UPDATE. А ТАКЖЕ УСЛОВИЕ, ЧТО БАЛАНС НЕ МОЖЕТ ОПУСКАТЬСЯ НИЖЕ НУЛЯ, ИНАЧЕ ОТКЛОНЕНИЕ ТРАНЗАКЦИИ. |