Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Транзакция в базах данных
Транзакция — это набор операций БД, которые либо все выполнятся успешно, либо все откатятся. Это гарантирует консистентность данных.
ACID свойства
Любая транзакция должна соответствовать ACID:
Atomicity (Атомарность)
- Либо все операции выполнены, либо ни одна
- Нет промежуточных состояний
// Перевод денег со счёта A на счёт B
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; // A
UPDATE accounts SET balance = balance + 100 WHERE id = 2; // B
COMMIT; // Оба выполнились
// Если ошибка между UPDATE:
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Ошибка!
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
ROLLBACK; // Оба откатились, A вернул деньги
Consistency (Консистентность)
- БД переходит из одного консистентного состояния в другое
- Все business rules соблюдаются
// Constraint: сумма на счётах не может быть отрицательной
BEGIN TRANSACTION;
UPDATE accounts SET balance = -50 WHERE id = 1; // Нарушает constraint
ROLLBACK; // Откатится, потому что нарушает консистентность
Isolation (Изоляция)
- Параллельные транзакции не мешают друг другу
- Каждая видит консистентную версию данных
// Транзакция 1: читает баланс = 1000
// Транзакция 2: вычитает 100 (баланс становится 900)
// Транзакция 1: видит 1000 (изолирована)
Durability (Долговечность)
- После коммита данные сохранены
- Даже при сбое сервера данные не потеряются
BEGIN TRANSACTION;
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT; // Данные записаны на диск
// Сервер падает — данные в безопасности
Примеры в Node.js
TypeORM (QueryRunner)
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// Операции в транзакции
const account1 = await queryRunner.manager.findOne(Account, { where: { id: 1 } });
const account2 = await queryRunner.manager.findOne(Account, { where: { id: 2 } });
account1.balance -= 100;
account2.balance += 100;
await queryRunner.manager.save(account1);
await queryRunner.manager.save(account2);
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
Sequelize
const transaction = await sequelize.transaction();
try {
const account1 = await Account.findByPk(1, { transaction });
const account2 = await Account.findByPk(2, { transaction });
await account1.update({ balance: account1.balance - 100 }, { transaction });
await account2.update({ balance: account2.balance + 100 }, { transaction });
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
Raw SQL
const connection = await pool.connect();
try {
await connection.query('BEGIN');
await connection.query(
'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
[100, 1]
);
await connection.query(
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
[100, 2]
);
await connection.query('COMMIT');
} catch (err) {
await connection.query('ROLLBACK');
throw err;
} finally {
connection.release();
}
Уровни изоляции
Read Uncommitted (грязное чтение возможно)
Транзакция 1: UPDATE balance = 900
Транзакция 2: читает 900 (до коммита Т1)
Транзакция 1: ROLLBACK → баланс снова 1000
Транзакция 2: видела несуществующее значение
Read Committed (по умолчанию в PostgreSQL)
Транзакция 1: UPDATE balance = 900
Транзакция 2: ждёт коммита Т1, потом читает 900
Repeatable Read (PostgreSQL по умолчанию для transactions)
Транзакция 1: читает баланс = 1000
Транзакция 2: UPDATE balance = 900, COMMIT
Транзакция 1: повторно читает — всё ещё видит 1000 (снимок)
Serializable (максимальная изоляция)
Транзакции выполняются как будто по очереди
Медленнее, но самое безопасное
Проблемы
Deadlock
Транзакция 1: заблокировала строку A, ждёт строку B
Транзакция 2: заблокировала строку B, ждёт строку A
→ Deadlock! Одна откатится
Lost Update
Транзакция 1: читает balance = 1000
Транзакция 2: читает balance = 1000
Транзакция 1: пишет balance = 900, коммит
Транзакция 2: пишет balance = 950, коммит
→ потеряется обновление Т1
Phantom Read
Транзакция 1: SELECT * WHERE age > 18 → 5 rows
Транзакция 2: INSERT new user with age > 18, COMMIT
Транзакция 1: SELECT * WHERE age > 18 → 6 rows (фантом!)
Практические советы
✅ Делай транзакции короткими ✅ Избегай блокировок больших таблиц ✅ Используй appropriate isolation level ✅ Обрабатывай ошибки и rollback ✅ Логируй проблемы для анализа
❌ Не оставляй транзакции открытыми ❌ Не делай долгих операций в транзакции ❌ Не забывай про error handling