Примеры транзакций PHP + MySQL

272

Я действительно не нашел нормального примера PHP файла, в котором используются транзакции MySQL. Можете ли вы показать мне простой пример этого?

И еще один вопрос. Я уже много программировал и не использовал транзакции. Могу ли я поместить функцию PHP или что-то в header.php, что если один mysql_query выходит из строя, то остальные тоже не работают?


Я думаю, что понял, правильно?:

mysql_query("SET AUTOCOMMIT=0");
mysql_query("START TRANSACTION");

$a1 = mysql_query("INSERT INTO rarara (l_id) VALUES('1')");
$a2 = mysql_query("INSERT INTO rarara (l_id) VALUES('2')");

if ($a1 and $a2) {
    mysql_query("COMMIT");
} else {        
    mysql_query("ROLLBACK");
}
  • 9
    Вы можете использовать mysql_query("BEGIN"); вместо последовательности mysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
  • 73
    Пожалуйста, не используйте функции mysql_* в новом коде . Они больше не поддерживаются и официально устарели . Видишь красную коробку ? Вместо этого узнайте о готовых утверждениях и используйте PDO или MySQLi - эта статья поможет вам решить, какие именно. Если вы выбираете PDO, вот хороший урок .
Показать ещё 6 комментариев
Теги:
transactions

9 ответов

312
Лучший ответ

Идея, которую я обычно использую при работе с транзакциями, выглядит так (полу-псевдокод):

try {
    // First of all, let begin a transaction
    $db->beginTransaction();

    // A set of queries; if one fails, an exception should be thrown
    $db->query('first query');
    $db->query('second query');
    $db->query('third query');

    // If we arrive here, it means that no exception was thrown
    // i.e. no query has failed, and we can commit the transaction
    $db->commit();
} catch (Exception $e) {
    // An exception has been thrown
    // We must rollback the transaction
    $db->rollback();
}


Обратите внимание, что с этой идеей, если запрос не выполняется, необходимо исключить Exception:

  • PDO может это сделать, в зависимости от того, как вы его настраиваете
  • else, с некоторым другим API, вам, возможно, придется проверить результат функции, используемой для выполнения запроса, и самостоятельно выбросить исключение.


К сожалению, магии нет. Вы не можете просто поместить инструкцию где-нибудь и выполнить транзакции автоматически: вам все равно нужно указать, какая группа запросов должна выполняться в транзакции.

Например, довольно часто у вас будет пара запросов перед транзакцией (до begin) и еще одна пара запросов после транзакции (после commit или rollback), и вы захотите эти запросы выполняются независимо от того, что произошло (или нет) в транзакции.

  • 32
    Будьте осторожны, если вы делаете операции, которые могут генерировать исключения, отличные от db. Если это так, исключение из оператора не-db может вызвать непреднамеренный откат (даже если все вызовы db выполнены успешно). Обычно вы можете подумать, что откат является хорошей идеей, даже если ошибка была не на стороне базы данных, но бывают случаи, когда сторонний / некритический код может вызвать не столь важные исключения, и вы все равно хотите продолжить перевод.
  • 6
    Какой тип $db здесь? Mysqli?
Показать ещё 8 комментариев
101

Я думаю, что понял, правильно?:

mysql_query("START TRANSACTION");

$a1 = mysql_query("INSERT INTO rarara (l_id) VALUES('1')");
$a2 = mysql_query("INSERT INTO rarara (l_id) VALUES('2')");

if ($a1 and $a2) {
    mysql_query("COMMIT");
} else {        
    mysql_query("ROLLBACK");
}
  • 25
    нет необходимости устанавливать autocommit = 0. транзакции всегда работают таким образом.
  • 1
    @babonk - не уверен, что это так с InnoDB?
Показать ещё 5 комментариев
36
<?php

// trans.php
function begin(){
    mysql_query("BEGIN");
}

function commit(){
    mysql_query("COMMIT");
}

function rollback(){
    mysql_query("ROLLBACK");
}

mysql_connect("localhost","Dude1", "SuperSecret") or die(mysql_error());

mysql_select_db("bedrock") or die(mysql_error());

$query = "INSERT INTO employee (ssn,name,phone) values ('123-45-6789','Matt','1-800-555-1212')";

begin(); // transaction begins

$result = mysql_query($query);

if(!$result){
    rollback(); // transaction rolls back
    echo "transaction rolled back";
    exit;
}else{
    commit(); // transaction is committed
    echo "Database transaction was successful";
}

?>
  • 0
    Для такого широкого и громкого вопроса, как этот, было бы здорово, если бы ответы также отражали это. Ваш пример кода великолепен, но можете ли вы рассказать подробнее? Объясните о сделках, почему, когда и где? Наконец, свяжите код с вашим объяснением.
  • 3
    Добро пожаловать на StackOverflow. Пожалуйста, всегда пишите текст описания к вашему ответу.
Показать ещё 3 комментария
35

Поскольку это первый результат в google для "транзакции php mysql", я подумал, что добавлю ответ, который явно демонстрирует, как это сделать с помощью mysqli (поскольку оригинальные авторы хотели примеров). Здесь упрощенный пример транзакций с PHP/mysqli:

// let pretend that a user wants to create a new "group". we will do so
// while at the same time creating a "membership" for the group which
// consists solely of the user themselves (at first). accordingly, the group
// and membership records should be created together, or not at all.
// this sounds like a job for: TRANSACTIONS! (*cue music*)

$group_name = "The Thursday Thumpers";
$member_name = "EleventyOne";
$conn = new mysqli($db_host,$db_user,$db_passwd,$db_name); // error-check this

// note: this is meant for InnoDB tables. won't work with MyISAM tables.

try {

    $conn->autocommit(FALSE); // i.e., start transaction

    // assume that the TABLE groups has an auto_increment id field
    $query = "INSERT INTO groups (name) ";
    $query .= "VALUES ('$group_name')";
    $result = $conn->query($query);
    if ( !$result ) {
        $result->free();
        throw new Exception($conn->error);
    }

    $group_id = $conn->insert_id; // last auto_inc id from *this* connection

    $query = "INSERT INTO group_membership (group_id,name) ";
    $query .= "VALUES ('$group_id','$member_name')";
    $result = $conn->query($query);
    if ( !$result ) {
        $result->free();
        throw new Exception($conn->error);
    }

    // our SQL queries have been successful. commit them
    // and go back to non-transaction mode.

    $conn->commit();
    $conn->autocommit(TRUE); // i.e., end transaction
}
catch ( Exception $e ) {

    // before rolling back the transaction, you'd want
    // to make sure that the exception was db-related
    $conn->rollback(); 
    $conn->autocommit(TRUE); // i.e., end transaction   
}

Кроме того, имейте в виду, что PHP 5.5 имеет новый метод mysqli:: begin_transaction. Однако это еще не задокументировано командой PHP, и я все еще придерживаюсь PHP 5.3, поэтому я не могу прокомментировать это.

  • 2
    В связи с этим я только что обнаружил, что если вы работаете с таблицами InnoDB, то есть возможность блокировать / разблокировать таблицы при использовании подхода autocomitt () к транзакциям, но это невозможно при использовании подхода begin_transaction (): MySQL документация
  • 0
    +1 за подробный (и прокомментированный) пример с актуальным кодом mysqli. Спасибо за это. И ваше мнение о блокировке / транзакциях действительно очень интересно.
Показать ещё 4 комментария
7

Пожалуйста, проверьте, какой механизм хранения вы используете. Если это MyISAM, то Transaction('COMMIT','ROLLBACK') не будет поддерживаться, потому что только механизм хранения InnoDB, а не MyISAM, поддерживает транзакции.

5

Я сделал функцию для получения вектора запросов и совершил транзакцию, может быть, кто-то узнает, что это полезно:

function transaction ($con, $Q){
        mysqli_query($con, "START TRANSACTION");

        for ($i = 0; $i < count ($Q); $i++){
            if (!mysqli_query ($con, $Q[$i])){
                echo 'Error! Info: <' . mysqli_error ($con) . '> Query: <' . $Q[$i] . '>';
                break;
            }   
        }

        if ($i == count ($Q)){
            mysqli_query($con, "COMMIT");
            return 1;
        }
        else {
            mysqli_query($con, "ROLLBACK");
            return 0;
        }
    }
4

При использовании соединения PDO:

$pdo = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8', $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // this is important
]);

Я часто использую следующий код для управления транзакциями:

function transaction(Closure $callback)
{
    global $pdo; // let assume our PDO connection is in a global var

    // start the transaction outside of the try block, because
    // you don't want to rollback a transaction that failed to start
    $pdo->beginTransaction(); 
    try
    {
        $callback();
        $pdo->commit(); 
    }
    catch (Exception $e) // it better to replace this with Throwable on PHP 7+
    {
        $pdo->rollBack();
        throw $e; // we still have to complain about the exception
    }
}

Пример использования:

transaction(function()
{
    global $pdo;

    $pdo->query('first query');
    $pdo->query('second query');
    $pdo->query('third query');
});

Таким образом, код транзакции не дублируется в проекте. Это хорошо, потому что, судя по другим PDO-решениям в этой теме, легко ошибиться в этом. Наиболее распространенными из них являются забывание о восстановлении исключения и начале транзакции внутри блока try.

3

У меня было это, но не уверен, что это правильно. Мог бы попробовать это также.

mysql_query("START TRANSACTION");
$flag = true;
$query = "INSERT INTO testing (myid) VALUES ('test')";

$query2 = "INSERT INTO testing2 (myid2) VALUES ('test2')";

$result = mysql_query($query) or trigger_error(mysql_error(), E_USER_ERROR);
if (!$result) {
$flag = false;
}

$result = mysql_query($query2) or trigger_error(mysql_error(), E_USER_ERROR);
if (!$result) {
$flag = false;
}

if ($flag) {
mysql_query("COMMIT");
} else {        
mysql_query("ROLLBACK");
}

Идея отсюда: http://www.phpknowhow.com/mysql/transactions/

  • 0
    Неправильный код. trigger_error вернет true (если вы не испортили свой вызов), поэтому $ result всегда будет true, поэтому этот код пропустит любой неудачный запрос и всегда попытается зафиксировать. Точно так же беспокоит то, что вы используете устаревший устаревший mysql_query вместо mysqli , даже если вы mysql_query на учебник, использующий mysqli . ИМХО, вы должны либо удалить этот плохой пример, либо заменить его, чтобы использовать код, как написано в учебнике phpknowhow.
1

Еще один пример процедурного стиля с mysqli_multi_query, предполагает $query, заполняется выражениями, разделенными запятой.

mysqli_begin_transaction ($link);

for (mysqli_multi_query ($link, $query);
    mysqli_more_results ($link);
    mysqli_next_result ($link) );

! mysqli_errno ($link) ?
    mysqli_commit ($link) : mysqli_rollback ($link);
  • 0
    Несколько странный код, но, по крайней мере, он предполагает использование mysqli_multi_query . Любой, кто заинтересован в этом, должен гуглить в другом месте для более чистого примера.
Сообщество Overcoder
Наверх
Меню