МДК.11.01 - 15 - Расширенные транзакции
Примеры данной темы используют учебную БД:
SET foreign_key_checks = 0;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS accounts;
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`)
);
LOCK TABLES `users` WRITE;
INSERT INTO `users` VALUES (1,'Иванов Иван Иванович'),(2,'Петров Петр Петрович'),(3,'Сидоров Сидор Сидорович');
UNLOCK TABLES;
CREATE TABLE `accounts` (
`id` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) DEFAULT NULL,
`user_id` INT NOT NULL,
`balance` DECIMAL(10,2) NOT NULL DEFAULT '0.00',
PRIMARY KEY (`id`),
KEY `user_id_idx` (`user_id`),
CONSTRAINT `user_id`
FOREIGN KEY (`user_id`)
REFERENCES `users` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE
);
LOCK TABLES `accounts` WRITE;
INSERT INTO `accounts` VALUES (1,'Основной',1,10500.00),(2,'Резервный',1,3412.57),(3,'Основной',2,20750.00),(4,'Основной',3,25000.00),(5,'Накопительный',2,5401.75);
UNLOCK TABLES;
SET foreign_key_checks = 1;
| id | name |
|---|---|
| 1 | Иванов Иван Иванович |
| 2 | Петров Петр Петрович |
| 3 | Сидоров Сидор Сидорович |
| id | title | user_id | balance |
|---|---|---|---|
| 1 | Основной | 1 | 10500 |
| 2 | Резервный | 1 | 3412.57 |
| 3 | Основной | 2 | 20750 |
| 4 | Основной | 3 | 25000 |
| 5 | Накопительный | 2 | 5401.75 |
Уровни изоляции транзакций
В MySQL 8 существует четыре уровня изоляции транзакций: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ и SERIALIZABLE.
Все примеры ниже будут использовать код двух транзакций:
Транзакция 1:
START TRANSACTION;
UPDATE accounts SET balance = balance + 1000 WHERE id = 1;
Транзакция 2:
START TRANSACTION;
UPDATE accounts SET balance = balance + 250 WHERE id = 1;
COMMIT;
Уровни изоляции позволяют настроить баланс между производительностью и надежностью транзакций в зависимости от требований приложения.
READ UNCOMMITTED(неподтвержденное чтение): Этот уровень позволяет одной транзакции читать неподтвержденные изменения, внесенные другой транзакцией.
Чтобы задать соответствующий уровень, после начала транзакции, необходимо его установить командой:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Если транзакция 1 выполняется в режиме READ UNCOMMITTED, она сможет прочитать изменения, внесенные транзакцией 2, даже до ее подтверждения.
READ COMMITTED(подтвержденное чтение): Этот уровень позволяет транзакции читать только подтвержденные изменения других транзакций.
Чтобы задать соответствующий уровень, после начала транзакции, необходимо его установить командой:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
Если транзакция 1 выполняется в режиме READ COMMITTED, она не сможет прочитать изменения, внесенные транзакцией 2, до ее подтверждения.
REPEATABLE READ(повторяемое чтение): Этот уровень гарантирует, что транзакция будет видеть одни и те же данные при повторном чтении в рамках одной транзакции.
Чтобы задать соответствующий уровень, после начала транзакции, необходимо его установить командой:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Если транзакция 1 выполняется в режиме REPEATABLE READ, она будет видеть исходные данные при повторном чтении, даже после подтверждения изменений транзакцией 2.
SERIALIZABLE(сериализуемое чтение): Этот уровень гарантирует полную изоляцию транзакций, предотвращая конфликты и аномалии.
Чтобы задать соответствующий уровень, после начала транзакции, необходимо его установить командой:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Если транзакция 1 выполняется в режиме SERIALIZABLE, она будет заблокирована до подтверждения изменений транзакцией 2, чтобы избежать возможных конфликтов.
Возможные аномалии по уровням
| Уровень изоляции | Потерянное обновление | «Грязное» чтение | Неповторяющееся чтение | Чтение «фантомов» |
|---|---|---|---|---|
NONE | ДА | ДА | ДА | ДА |
READ UNCOMMITTED | НЕТ | ДА | ДА | ДА |
READ COMMITTED | НЕТ | НЕТ | ДА | ДА |
REPEATABLE READ | НЕТ | НЕТ | НЕТ | ДА |
SERIALIZABLE | НЕТ | НЕТ | НЕТ | НЕТ |
Потерянное обновление — Ситуация, когда при одновременном изменении одного блока данных разными транзакциями одно из изменений теряется.
«Грязное» чтение — Чтение данных, добавленных или изменённых транзакцией, которая впоследствии не подтвердится (откатится).
Неповторяющееся чтение — Ситуация, когда при повторном чтении в рамках одной транзакции ранее прочитанные данные оказываются изменёнными.
Чтение «фантомов» — Ситуация, когда при повторном чтении в рамках одной транзакции одна и та же выборка дает разные множества строк.
Уровень изоляции по-умолчанию
Чтобы узнать уровень изоляции транзакций, используемый по умолчанию в MySQL 8, можно выполнить следующий запрос:
SELECT @@global.transaction_isolation;
Этот запрос вернет текущее значение переменной transaction_isolation, которая указывает на уровень изоляции транзакций, используемый по умолчанию. Возможные значения переменной transaction_isolation включают READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE и NONE.
Примечание
Обратите внимание, что доступ к этой информации может потребовать привилегий администратора базы данных.
Работа с точками восстановления
Точка восстановления, или SAVEPOINT — это зафиксированное состояние БД к которому можно откатиться в рамках команды ROLLBACK.
Допустим, пользователь Иванов Иван Иванович переводит деньги сразу нескольким пользователям в рамках одной "сделки". Логично оформить набор запросов в виде единой транзакции, но при этом зафиксировать состояния внутри неё, чтобы иметь возможность откатиться на любой шаг.
- Начинаем транзакцию:
START TRANSACTION;
- Получаем текущий баланс Иванова и номер его основного счета:
SELECT id, balance FROM accounts WHERE user_id = 1 AND title = 'Основной';
- Фиксируем состояния до того как начали перевод средств:
SAVEPOINT SavePoint1;
- Переводим
5000рублей с основного счёта Иванова на основной счёт Петрова:
UPDATE accounts SET balance = balance - 5000 WHERE id = 1;
UPDATE accounts SET balance = balance + 5000 WHERE id = 3;
- Фиксируем состояние в этой точке:
SAVEPOINT SavePoint2;
- Переводим
1500рублей с основного счёта Иванова на основной счёт Сидорова:
UPDATE accounts SET balance = balance - 1500 WHERE id = 1;
UPDATE accounts SET balance = balance + 1500 WHERE id = 4;
- Фиксируем состояние в этой точке:
SAVEPOINT SavePoint3;
- Посмотрим сколько денег осталось на основном счёте Иванова:
SELECT id, balance FROM accounts WHERE user_id = 1 AND title = 'Основной';
На данный момент у нас есть возможность произвести следующие действия:
- Если всё прошло хорошо, и результаты всех действий нас устроили, можем зафиксировать результаты при помощи
COMMIT;. - Если нам нужно откатиться на состояние после того как мы перевели деньги Сидорову, можем выполнить
ROLLBACK TO SavePoint3;. - Если нам нужно откатиться на состояние после того как мы перевели деньги Петрову, но до того как мы начали переводить деньги Сидорову, можем выполнить
ROLLBACK TO SavePoint2;. - Если нам нужно откатиться на состояние до того как мы начали переводить деньги, можем выполнить
ROLLBACK TO SavePoint1;. - Если нужно вернуть БД в состояние как будто целой транзакции не было, используем просто
ROLLBACK;, при этом промежуточные состояния точек восстановления учтены не будут. - Если какая-либо из точек восстановления уже не нужна, очистить её из памяти можно при помощи команды
RELEASE SAVEPOINT SavePoint1;.
Примечание
Важно, что откатывая состояние на более раннюю точку восстановления, мы также лишаем себя возможности восстановиться на более поздней. То есть, возвращаясь в более раннюю точку мы фактически возвращаемся в состояние в котором более поздних просто не существовало!
Инфо
Стоит учесть что в большинстве СУБД после закрытия транзакции, откатиться на точку восстановления уже не выйдет, поэтому для сохранения состояния БД между транзакциями используются уже другие механизмы.