Загрузка данных
Практическая работа: «Работа с транзакциями в MySQL Workbench»
Дисциплина: МДК11
Тема: Реализация транзакций и управление ими в среде MySQL Workbench
Цель работы: освоить инструменты MySQL Workbench для написания, отладки и контроля транзакций; научиться использовать автокоммит, точки сохранения и уровни изоляции, а также анализировать поведение блокировок.
Продолжительность: 2 академических часа (90 минут)
Среда выполнения: MySQL Server + MySQL Workbench, движок таблиц InnoDB
Подготовка к работе
Перед началом убедимся, что таблицы используют движок InnoDB — без него транзакции работать не будут. Выполните в окне запроса:
SHOW CREATE TABLE accounts;
Если в выводе указано ENGINE=InnoDB, всё в порядке. Если MyISAM, измените движок:
ALTER TABLE accounts ENGINE = InnoDB;
Создадим тестовую базу и таблицы. Сначала создайте и выберите базу:
CREATE DATABASE IF NOT EXISTS lab_transactions;
USE lab_transactions;
Не забудьте нажать обновление в столбце с БД!!!
Затем создайте таблицу счетов:
CREATE TABLE accounts (
id INT PRIMARY KEY,
name VARCHAR(100),
balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00
) ENGINE=InnoDB;
Создайте таблицу лога транзакций:
CREATE TABLE transactions_log (
id INT AUTO_INCREMENT PRIMARY KEY,
from_account INT,
to_account INT,
amount DECIMAL(10, 2),
status ENUM('pending', 'completed', 'rolled_back') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
Заполните начальные данные:
INSERT INTO accounts (id, name, balance) VALUES
(1, 'Alice', 1000.00),
(2, 'Bob', 500.00),
(3, 'Charlie', 200.00);
Проверьте текущие настройки сессии — выполните:
SELECT @@autocommit, @@transaction_isolation;
Запишите значения — они понадобятся в отчёте.
Задание 1. Автокоммит и ручное управление транзакциями
Задача: разобраться, как Workbench обрабатывает автокоммит и как вручную управлять фиксацией и откатом транзакций.
Отключите автокоммит:
SET autocommit = 0;
Начните транзакцию:
START TRANSACTION;
Выполните перевод 200 рублей со счёта 1 на счёт 2:
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
Пока НЕ делайте COMMIT. Откройте вторую вкладку запроса (кнопка «+» вверху Workbench) — это будет независимая сессия — и выполните:
SELECT * FROM accounts;
Вы увидите, что во второй вкладке баланс Alice по-прежнему 1000, а Bob — 500. Изменения из первой сессии ещё не видны, потому что транзакция не зафиксирована.
Вернитесь в первую вкладку и выполните:
COMMIT;
Теперь во второй вкладке снова запустите:
SELECT * FROM accounts;
Баланс Alice станет 800, а Bob — 700. Изменения зафиксированы и видны всем.
Повторите эксперимент, но вместо COMMIT сделайте ROLLBACK. Начните новую транзакцию:
START TRANSACTION;
Выполните перевод 100 рублей со счёта 1 на счёт 3:
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 3;
Сделайте откат:
ROLLBACK;
Проверьте баланс — он вернулся к значениям после первого перевода. Все изменения отменены.
Что зафиксировать в отчёте:
• Скриншоты: состояние баланса до транзакции, во время (во второй вкладке), после COMMIT, после ROLLBACK.
• Объяснение: почему во второй вкладке изменения не видны до COMMIT.
Задание 2. Точки сохранения (SAVEPOINT)
Задача: научиться откатывать не всю транзакцию, а только часть операций.
Представьте сценарий: вы списываете деньги, зачисляете на другой счёт, а затем выполняете «рисковую» операцию, которая может не пройти. Если она не прошла — нужно откатить только её, сохранив первые две.
Начните транзакцию:
START TRANSACTION;
Операция 1 — списать 100 со счёта 1:
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
Операция 2 — зачислить 100 на счёт 3:
UPDATE accounts SET balance = balance + 100 WHERE id = 3;
Запишите в лог:
INSERT INTO transactions_log (from_account, to_account, amount, status)
VALUES (1, 3, 100, 'pending');
Поставьте точку сохранения перед рисковой операцией:
SAVEPOINT sp_before_risky;
Выполните рисковую операцию — списать ещё 50 со счёта 1:
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
Допустим, операция оказалась неудачной — откатываемся к точке:
ROLLBACK TO SAVEPOINT sp_before_risky;
Альтернативное действие — зачисляем эти 50 на счёт 2:
UPDATE accounts SET balance = balance + 50 WHERE id = 2;
Обновляем статус в логе:
UPDATE transactions_log SET status = 'completed'
WHERE from_account = 1 AND to_account = 3;
Завершаем транзакцию:
COMMIT;
Чтобы лучше понять, что происходит, выполняйте скрипт по шагам. После ROLLBACK TO SAVEPOINT вставьте:
SELECT * FROM accounts;
Вы увидите, что списание 50 со счёта 1 отменено, а первые операции (перевод 100 и запись в лог) сохранены.
Что зафиксировать в отчёте:
• Скриншоты баланса после ROLLBACK TO SAVEPOINT и после COMMIT.
• Объяснение: чем ROLLBACK TO SAVEPOINT отличается от полного ROLLBACK и зачем это нужно.
Задание 3. Уровни изоляции транзакций
Задача: увидеть разницу между REPEATABLE READ и READ COMMITTED на практике.
Эксперимент 1: REPEATABLE READ
Откройте две отдельные вкладки в Workbench — это две независимые сессии.
В первой вкладке задайте уровень изоляции:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Начните транзакцию:
START TRANSACTION;
Прочитайте баланс Alice:
SELECT id, name, balance FROM accounts WHERE id = 1;
Запишите результат (должно быть 800).
Во второй вкладке измените баланс и зафиксируйте:
UPDATE accounts SET balance = balance + 10 WHERE id = 1;
COMMIT;
Вернитесь в первую вкладку и снова выполните:
SELECT id, name, balance FROM accounts WHERE id = 1;
Баланс Alice по-прежнему 800 — хотя в базе он уже 810. При REPEATABLE READ транзакция «видит» данные так, какими они были на момент первого чтения.
Завершите транзакцию:
COMMIT;
Теперь выполните SELECT ещё раз — баланс станет 810.
Эксперимент 2: READ COMMITTED
В первой вкладке задайте другой уровень изоляции:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Начните транзакцию:
START TRANSACTION;
Прочитайте баланс:
SELECT id, name, balance FROM accounts WHERE id = 1;
Запишите баланс (810).
Во второй вкладке снова измените баланс и зафиксируйте:
UPDATE accounts SET balance = balance + 10 WHERE id = 1;
COMMIT;
Вернитесь в первую вкладку и выполните:
SELECT id, name, balance FROM accounts WHERE id = 1;
Теперь баланс уже 820 — при READ COMMITTED транзакция видит изменения, зафиксированные другими транзакциями, даже если первое чтение было раньше.
Завершите:
COMMIT;
Что зафиксировать в отчёте:
• Скриншоты обоих чтений в первой вкладке для каждого эксперимента.
• Объяснение: в чём разница между уровнями и какой уровень в каких ситуациях предпочтительнее.
Задание 4. Явные блокировки (FOR UPDATE)
Задача: увидеть, как FOR UPDATE удерживает блокировку и заставляет другие сессии ждать.
В первой вкладке отключите автокоммит:
SET autocommit = 0;
Начните транзакцию:
START TRANSACTION;
Заблокируйте строку счёта 1:
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
MySQL заблокировал строку счёта 1. Пока вы не сделаете COMMIT или ROLLBACK, никто другой не может изменить эту строку.
Во второй вкладке попробуйте выполнить то же самое:
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
Запрос «зависнет» — он будет ждать снятия блокировки. В Workbench это выглядит как крутящийся значок выполнения с надписью «Query in progress».
Вернитесь в первую вкладку и выполните обновление:
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
Зафиксируйте:
COMMIT;
Сразу после COMMIT во второй вкладке запрос выполнится — вы увидите обновлённый баланс (770, так как 820 − 50 = 770).
Что зафиксировать в отчёте:
• Скриншот «зависшего» запроса во второй вкладке.
• Скриншот результата после COMMIT в первой вкладке.
• Объяснение: зачем нужен FOR UPDATE и что было бы без него (две транзакции могли бы одновременно прочитать баланс и обе сделать списание — в итоге деньги «потерялись» бы).
Задание 5. Диагностика транзакций и блокировок
Задача: научиться видеть активные транзакции и блокировки через инструменты Workbench.
Повторите эксперимент из Задания 4: в первой вкладке выполните блокировку строки без COMMIT:
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
Затем в третьей вкладке выполните диагностический запрос — он покажет все активные транзакции с их идентификаторами, временем старта и состоянием:
SELECT * FROM information_schema.innodb_trx;
Если во второй вкладке запрос ждёт блокировку, выполните запрос, который покажет, кто кого ждёт:
SELECT * FROM information_schema.innodb_lock_waits;
Также в Workbench можно открыть визуальный монитор: меню Server → Dashboard, вкладка InnoDB. Там отображаются активные транзакции и блокировки в графическом виде.
После диагностики не забудьте завершить транзакцию в первой вкладке:
COMMIT;
Что зафиксировать в отчёте:
• Скриншот результата information_schema.innodb_trx с активной транзакцией.
• Скриншот Dashboard или innodb_lock_waits (если есть ожидание).
• Краткое описание: что показывают эти инструменты и как они помогают при отладке.
Контрольные вопросы
1. Что означает ACID применительно к транзакциям в MySQL?
2. Чем отличается ROLLBACK от ROLLBACK TO SAVEPOINT? Приведите пример сценария для каждого.
3. Почему при REPEATABLE READ повторное чтение в одной транзакции даёт тот же результат, даже если данные уже изменились?
4. Что произойдёт, если в одной вкладке выполнить SELECT ... FOR UPDATE, а во второй — обычный SELECT (без FOR UPDATE)? Почему?
5. Что такое deadlock и как MySQL Workbench помогает его обнаружить?
Требования к отчёту
Отчёт должен содержать:
• Цель работы и описание среды (версия MySQL, версия Workbench, движок InnoDB).
• SQL-скрипты по каждому заданию — можно переписать из текста работы.
• Скриншоты: баланс до и после операций (Задание 1); состояние после ROLLBACK TO SAVEPOINT (Задание 2); два чтения при разных уровнях изоляции (Задание 3); «зависший» запрос и результат после разблокировки (Задание 4); активная транзакция в information_schema (Задание 5).
• Выводы по каждому заданию: что получилось, какие проблемы решаются с помощью транзакций.
• Ответы на контрольные вопросы.
Критерии оценки
• Отлично: все 5 заданий выполнены, отчёт содержит скрипты, скриншоты, чёткие выводы; ответы на вопросы полные и корректные.
• Хорошо: задания выполнены, но есть небольшие неточности в отчёте или ответах.
• Удовлетворительно: часть заданий выполнена, отчёт неполный.
• Неудовлетворительно: задания не выполнены или отчёт отсутствует.
Методические подсказки
• Каждая вкладка в Workbench — это отдельная сессия. Используйте это для эмуляции параллельных транзакций.
• Не оставляйте транзакции незавершёнными. Если забыть COMMIT или ROLLBACK, блокировки будут удерживаться, а другие сессии — ждать.
• Проверяйте движок таблицы. Если таблица на MyISAM, ROLLBACK не сработает, а FOR UPDATE не заблокирует строку.
• Если Workbench выдаёт ошибку таймаута при ожидании блокировки — увеличьте innodb_lock_wait_timeout:
SET SESSION innodb_lock_wait_timeout = 50;
Это даст больше времени на наблюдение за «зависшим» запросом.
Самостоятельное задание: «Списание бонусов и применение скидки»
Сценарий: В интернет магазине у клиента есть бонусный счёт и обычный баланс. При оформлении заказа система должна:
1. Списать бонусы с бонусного счёта клиента.
2. Начислить скидку на заказ (уменьшить итоговую сумму).
3. Записать факт операции в журнал транзакций.
Если на любом шаге произойдёт ошибка (например, бонусов недостаточно или запись в журнал не прошла), вся операция должна быть отменена.
Подготовка данных
Создайте базу и таблицы (убедитесь, что движок — InnoDB):
sql
CREATE DATABASE IF NOT EXISTS shop_demo;
USE shop_demo;
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100),
balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
bonus_balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00
) ENGINE=InnoDB;
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
amount DECIMAL(10, 2),
discount_amount DECIMAL(10, 2),
final_amount DECIMAL(10, 2),
status ENUM('pending', 'completed', 'cancelled') NOT NULL DEFAULT 'pending'
) ENGINE=InnoDB;
CREATE TABLE transactions_log (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
operation_type VARCHAR(50),
amount DECIMAL(10, 2),
description VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
Заполните начальные данные:
sql
INSERT INTO customers (id, name, balance, bonus_balance) VALUES
(1, 'Ivan', 5000.00, 1200.00);
________________________________________
Задание для студента
Выполните в MySQL Workbench следующую транзакцию. Не делайте COMMIT сразу, сначала прочитайте условие и попробуйте предсказать результат.
sql
SET autocommit = 0;
START TRANSACTION;
-- Шаг 1: проверяем, хватает ли бонусов (это можно сделать и в коде приложения,
-- но для тренировки сделаем проверку прямо в SQL через условие)
-- Для простоты считаем, что хотим списать 1000 бонусов.
-- Шаг 2: списываем бонусы
UPDATE customers
SET bonus_balance = bonus_balance - 1000
WHERE id = 1;
-- Шаг 3: создаём заказ на 4500 руб., применяем скидку 1000 руб. за счёт бонусов
INSERT INTO orders (customer_id, amount, discount_amount, final_amount, status)
VALUES (1, 4500.00, 1000.00, 3500.00, 'pending');
-- Получаем ID только что созданного заказа
SET @last_order_id = LAST_INSERT_ID();
-- Шаг 4: записываем операцию в лог
INSERT INTO transactions_log (customer_id, operation_type, amount, description)
VALUES (1, 'BONUS_WRITE_OFF', 1000.00, CONCAT('Списание бонусов для заказа ', @last_order_id));
-- Здесь представьте, что произошла ошибка (например, платёжный шлюз не ответил).
-- Чтобы имитировать ошибку, намеренно **не делайте COMMIT**, а вместо этого выполните:
ROLLBACK;
Что нужно сделать студенту
1. Запустить скрипт по частям. Сначала только START TRANSACTION и первый UPDATE, затем проверить баланс бонусов. Потом добавить INSERT в orders, проверить, появился ли заказ. Потом добавить вставку в transactions_log.
2. Выполнить ROLLBACK (как в примере) и убедиться, что:
o Бонусы вернулись к исходному значению.
o Заказ не остался в таблице orders (или его статус можно поменять на cancelled).
o Запись в transactions_log исчезла.
3. Повторить, но в конце сделать COMMIT вместо ROLLBACK — убедиться, что все изменения сохранились.
4. Добавить проверку на отрицательный баланс бонусов. Например, обернуть логику в условие или использовать ограничение на уровне БД (CHECK-ограничение в новых версиях MySQL или проверку в хранимой процедуре).
________________________________________
Контрольные вопросы к заданию
1. Почему важно, чтобы списание бонусов, создание заказа и запись в лог были в одной транзакции?
2. Что произойдёт, если забыть ROLLBACK при ошибке? Какие проблемы это вызовет в учёте бонусов и заказов?
3. Как бы вы защитили систему от списания большего количества бонусов, чем есть у клиента (валидация)?
4. Если бы вместо ROLLBACK вы сделали COMMIT, а потом обнаружили ошибку, можно ли было бы «откатить» эту транзакцию? Почему да или нет?
5. Какой уровень изоляции вы бы выбрали для таких операций и почему?
________________________________________
Подсказки и типичные ошибки
• Забыли отключить автокоммит. Если autocommit = 1, каждый UPDATE и INSERT фиксируется сразу, и ROLLBACK не отменит предыдущие шаги.
• Таблица не InnoDB. В MyISAM ROLLBACK ничего не делает. Проверьте: SHOW CREATE TABLE customers;.
• Неверный порядок операций. Сначала лучше проверять условия (хватает ли бонусов), потом менять данные, потом писать в лог.
• Потеря ID заказа. Если вы делаете несколько INSERT, используйте LAST_INSERT_ID() сразу после вставки в orders.