Как выполнить транзакцию в PHP+MySQL?php-20

Транзакции позволяют выполнять несколько SQL-запросов как единую атомарную операцию — либо все изменения применяются, либо ни одно из них. Вот как правильно работать с транзакциями.

Базовый подход с PDO

$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

try {
    // Начало транзакции
    $pdo->beginTransaction();

    // Выполнение операций
    $stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
    $stmt1->execute([100, 1]);

    $stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
    $stmt2->execute([100, 2]);

    // Если все успешно — коммитим
    $pdo->commit();

} catch (PDOException $e) {
    // При ошибке — откатываем
    $pdo->rollBack();
    error_log("Transaction failed: " . $e->getMessage());
}

Ключевые моменты транзакций

  1. ACID-принципы:

    • Атомарность — все или ничего
    • Согласованность — данные всегда в валидном состоянии
    • Изолированность — параллельные транзакции не мешают друг другу
    • Долговечность — после commit изменения сохраняются
  2. Уровни изоляции (можно задать перед beginTransaction):

    $pdo->exec("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
    
    • READ UNCOMMITTED — грязное чтение
    • READ COMMITTED — предотвращает грязное чтение
    • REPEATABLE READ — стандарт для InnoDB
    • SERIALIZABLE — максимальная изоляция

Транзакции в MySQLi

$mysqli = new mysqli('localhost', 'user', 'password', 'test');

try {
    // Отключаем autocommit
    $mysqli->autocommit(false);

    // Выполняем операции
    $mysqli->query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    $mysqli->query("UPDATE accounts SET balance = balance + 100 WHERE id = 2");

    // Проверяем на ошибки
    if (!$mysqli->errno) {
        $mysqli->commit();
    } else {
        throw new Exception($mysqli->error);
    }

} catch (Exception $e) {
    $mysqli->rollback();
    error_log("Transaction failed: " . $e->getMessage());

} finally {
    $mysqli->autocommit(true);
}

Практические рекомендации

  1. Обрабатывайте ошибки — всегда используйте try/catch
  2. Короткие транзакции — не держите транзакции открытыми долго
  3. Проверяйте affected rows — убедитесь, что запросы изменили то, что ожидали
    if ($stmt->rowCount() === 0) {
        throw new Exception("No rows affected");
    }
    
  4. Избегайте вложенных транзакций — MySQL их не поддерживает нативно

Пример с возвратом к началу точки сохранения

$pdo->beginTransaction();

try {
    // Точка сохранения
    $pdo->exec("SAVEPOINT part1");

    $pdo->exec("INSERT INTO orders (...) VALUES (...)");

    // Еще точка сохранения
    $pdo->exec("SAVEPOINT part2");

    $pdo->exec("UPDATE inventory SET quantity = quantity - 1");

    $pdo->commit();

} catch (Exception $e) {
    // Откат к последней точке сохранения
    $pdo->exec("ROLLBACK TO SAVEPOINT part1");

    // Альтернативная логика обработки
    $pdo->exec("INSERT INTO failed_orders (...) VALUES (...)");

    $pdo->commit();
}

Транзакции в Laravel

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);
    DB::table('posts')->delete();

    // Если возникнет исключение — автоматический откат
});

Резюмируем:

для работы с транзакциями в PHP+MySQL используйте beginTransaction(), commit() и rollBack() в PDO или autocommit(false), commit() и rollback() в MySQLi. Всегда обрабатывайте ошибки, проверяйте affected rows и держите транзакции максимально короткими. PDO предпочтительнее благодаря поддержке исключений и более чистому API.