Загрузка данных
/* =========================================================
ПРАКТИЧЕСКАЯ РАБОТА
Работа с транзакциями в MySQL
========================================================= */
/* =========================================================
ЧАСТЬ 1. LAB_TRANSACTIONS
========================================================= */
DROP DATABASE IF EXISTS lab_transactions;
CREATE DATABASE lab_transactions;
USE lab_transactions;
/* ---------- Создание таблиц ---------- */
CREATE TABLE accounts (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
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 * FROM accounts;
/* =========================================================
ЗАДАНИЕ 1. COMMIT
========================================================= */
SET autocommit = 0;
START TRANSACTION;
UPDATE accounts
SET balance = balance - 200
WHERE id = 1;
UPDATE accounts
SET balance = balance + 200
WHERE id = 2;
COMMIT;
/* Проверяем результат:
Alice = 800
Bob = 700
Charlie = 200
*/
SELECT * FROM accounts;
/* =========================================================
ЗАДАНИЕ 1. ROLLBACK
========================================================= */
START TRANSACTION;
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE id = 3;
ROLLBACK;
/* После ROLLBACK:
Alice = 800
Bob = 700
Charlie = 200
*/
SELECT * FROM accounts;
/* =========================================================
ЗАДАНИЕ 2. SAVEPOINT
========================================================= */
START TRANSACTION;
/* Операция 1 */
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
/* Операция 2 */
UPDATE accounts
SET balance = balance + 100
WHERE id = 3;
/* Записываем операцию в журнал */
INSERT INTO transactions_log
(from_account, to_account, amount, status)
VALUES
(1, 3, 100.00, 'pending');
/* Создаём точку сохранения */
SAVEPOINT sp_before_risky;
/* Рисковая операция */
UPDATE accounts
SET balance = balance - 50
WHERE id = 1;
/* Отменяем только рисковую операцию */
ROLLBACK TO SAVEPOINT sp_before_risky;
/* Альтернативная операция */
UPDATE accounts
SET balance = balance + 50
WHERE id = 2;
/* Меняем статус операции */
UPDATE transactions_log
SET status = 'completed'
WHERE from_account = 1
AND to_account = 3;
/* Фиксируем */
COMMIT;
/* Проверяем */
SELECT * FROM accounts;
SELECT * FROM transactions_log;
/* =========================================================
ЗАДАНИЕ 3. REPEATABLE READ
========================================================= */
/*
Полностью воспроизвести эксперимент с двумя вкладками
автоматически одним соединением невозможно.
Здесь выполняем транзакцию с REPEATABLE READ.
*/
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT id, name, balance
FROM accounts
WHERE id = 1;
COMMIT;
/* =========================================================
ЗАДАНИЕ 3. READ COMMITTED
========================================================= */
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT id, name, balance
FROM accounts
WHERE id = 1;
COMMIT;
/* =========================================================
ЗАДАНИЕ 4. FOR UPDATE
========================================================= */
SET autocommit = 0;
START TRANSACTION;
/* Блокируем счёт Alice */
SELECT balance
FROM accounts
WHERE id = 1
FOR UPDATE;
/* Списываем 50 */
UPDATE accounts
SET balance = balance - 50
WHERE id = 1;
/* Фиксируем */
COMMIT;
/* Проверяем.
После предыдущих операций баланс зависит от выполненных
операций, но списание 50 будет зафиксировано.
*/
SELECT * FROM accounts;
/* =========================================================
ЗАДАНИЕ 5. ДИАГНОСТИКА
========================================================= */
/* Проверка активных транзакций */
SELECT *
FROM information_schema.innodb_trx;
/* Проверка ожиданий блокировок.
В зависимости от версии MySQL эта таблица может отсутствовать.
*/
SELECT *
FROM information_schema.innodb_lock_waits;
/* =========================================================
ЧАСТЬ 2. САМОСТОЯТЕЛЬНОЕ ЗАДАНИЕ
Интернет-магазин
========================================================= */
DROP DATABASE IF EXISTS shop_demo;
CREATE DATABASE shop_demo;
USE shop_demo;
/* =========================================================
Создание таблицы клиентов
========================================================= */
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
balance DECIMAL(10,2) NOT NULL DEFAULT 0.00,
bonus_balance DECIMAL(10,2) NOT NULL DEFAULT 0.00,
CONSTRAINT chk_bonus_balance
CHECK (bonus_balance >= 0)
) ENGINE=InnoDB;
/* =========================================================
Создание таблицы заказов
========================================================= */
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
discount_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
final_amount DECIMAL(10,2) NOT NULL,
status ENUM('pending', 'completed', 'cancelled')
NOT NULL DEFAULT 'pending',
FOREIGN KEY (customer_id)
REFERENCES customers(id)
) ENGINE=InnoDB;
/* =========================================================
Создание журнала транзакций
========================================================= */
CREATE TABLE transactions_log (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
operation_type VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
description VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id)
REFERENCES customers(id)
) ENGINE=InnoDB;
/* =========================================================
Начальные данные
========================================================= */
INSERT INTO customers
(id, name, balance, bonus_balance)
VALUES
(1, 'Ivan', 5000.00, 1200.00);
/* Проверяем клиента */
SELECT * FROM customers;
/* =========================================================
СЦЕНАРИЙ 1. ТРАНЗАКЦИЯ С ROLLBACK
========================================================= */
SET autocommit = 0;
START TRANSACTION;
/* Блокируем клиента */
SELECT *
FROM customers
WHERE id = 1
FOR UPDATE;
/* Проверяем количество бонусов.
Должно быть минимум 1000.
*/
SELECT bonus_balance
FROM customers
WHERE id = 1;
/* Списываем 1000 бонусов */
UPDATE customers
SET bonus_balance = bonus_balance - 1000
WHERE id = 1
AND bonus_balance >= 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();
/* Записываем операцию в журнал */
INSERT INTO transactions_log
(
customer_id,
operation_type,
amount,
description
)
VALUES
(
1,
'BONUS_WRITE_OFF',
1000.00,
CONCAT(
'Списание бонусов для заказа ',
@last_order_id
)
);
/*
Имитируем ошибку.
Все изменения отменяются.
*/
ROLLBACK;
/* Проверяем результат */
SELECT * FROM customers;
SELECT * FROM orders;
SELECT * FROM transactions_log;
/* =========================================================
СЦЕНАРИЙ 2. ТА ЖЕ ОПЕРАЦИЯ С COMMIT
========================================================= */
START TRANSACTION;
/* Блокируем клиента */
SELECT *
FROM customers
WHERE id = 1
FOR UPDATE;
/* Проверяем бонусы */
SELECT bonus_balance
FROM customers
WHERE id = 1;
/* Списываем бонусы */
UPDATE customers
SET bonus_balance = bonus_balance - 1000
WHERE id = 1
AND bonus_balance >= 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();
/* Записываем операцию */
INSERT INTO transactions_log
(
customer_id,
operation_type,
amount,
description
)
VALUES
(
1,
'BONUS_WRITE_OFF',
1000.00,
CONCAT(
'Списание бонусов для заказа ',
@last_order_id
)
);
/* Заказ успешно завершён */
UPDATE orders
SET status = 'completed'
WHERE id = @last_order_id;
/* Фиксируем всю транзакцию */
COMMIT;
/* =========================================================
ФИНАЛЬНАЯ ПРОВЕРКА
========================================================= */
SELECT
id,
name,
balance,
bonus_balance
FROM customers;
SELECT
id,
customer_id,
amount,
discount_amount,
final_amount,
status
FROM orders;
SELECT
id,
customer_id,
operation_type,
amount,
description,
created_at
FROM transactions_log;
/* =========================================================
ПРОВЕРКА НАСТРОЕК
========================================================= */
SELECT
@@autocommit AS autocommit,
@@transaction_isolation AS transaction_isolation;
/* =========================================================
КОНЕЦ РАБОТЫ
========================================================= */