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

294

Я действительно не нашел нормальный пример файла 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");
}
Добрый вечер
источник
10
Вы можете использовать mysql_query("BEGIN");вместо последовательностиmysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Кирзилла
75
Пожалуйста, не используйте mysql_*функции в новом коде . Они больше не поддерживаются и официально устарели . Видишь красную коробку ? Вместо этогоузнайте о готовых утверждениях и используйте PDO или MySQLi - эта статья поможет вам решить, какие именно. Если вы выбираете PDO, вот хороший урок .
Нафтали ака Нил
6
Do "mysql_query (" SET AUTOCOMMIT = 0 ");" установить все соединения для ожидания функции фиксации или это только для связанного с ней соединения?
Хамид
1
@Neal, На самом деле mysqlВун умрет, несмотря на то, что он устарел, он будет доступен в PECL навсегда.
Pacerier
2
@Pacerier Вещи, которые осуждаются, не «умирают». Официально они хранятся для устаревшего программного обеспечения, но перестают обслуживаться и лишены любых рекомендуемых методов для нового программного обеспечения. Факт остается фактом, не используйтеmysql
taylorcressy

Ответы:

325

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

try {
    // First of all, let's 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();
}


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

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


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

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

Паскаль МАРТИН
источник
35
Будьте осторожны, если вы делаете операции, которые могут генерировать исключения, отличные от db. Если это так, исключение из оператора не-db может вызвать непреднамеренный откат (даже если все вызовы db выполнены успешно). Обычно вы можете подумать, что откат является хорошей идеей, даже если ошибка была не на стороне базы данных, но иногда сторонний / некритический код может вызывать не столь важные исключения, и вы все равно хотите продолжить перевод.
Халил Озгюр
6
Какой $dbтип здесь? MySQLi?
Джейк
3
@ Джейк Смотрите мой ответ для примера, который использует mysqli (похож по стилю на подход Паскаля).
EleventyOne
2
его можно легко модифицировать, чтобы перехватывать PDOExceptionи даже проверять значения исключений, если это необходимо. us2.php.net/PDOException
Yamiko
1
$ db - это объект PDO (соединение). Ссылка: php.net/manual/en/pdo.connections.php
Fil
110

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

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");
}
Добрый вечер
источник
26
нет необходимости устанавливать autocommit = 0. транзакции всегда работают таким образом.
bgcode
2
@babonk - не уверен, что это так с InnoDB?
buggedcom
6
Я думаю, что как только вы начинаете транзакцию, она работает так, как будто AUTOCOMMIT = 0
bgcode
4
@babonk прав. Как только транзакция запущена, AUTOCOMMIT = 0 устанавливается неявно, и после того, как транзакция заканчивается коммитом или откатом, MySql сбрасывает значение AUTOCOMMIT, которое использовалось до запуска транзакции. ПРИМЕЧАНИЕ. Вам НЕ следует устанавливать AUTOCOMMIT = 0, потому что после принятия изменений, если вы решите вставить / обновить другую строку, вы должны зафиксировать ее явно.
eroteev
4
Движок магазина должен быть InnoDB, а не MyISAM!
Джавад
39
<?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";
}

?>
Гедзберг Алекс
источник
Для такого широкого и громкого вопроса, как этот, было бы здорово, если бы ответы также отражали это. Ваш пример кода великолепен, но можете ли вы рассказать подробнее? Объясните о сделках, почему, когда и где? Наконец, свяжите код с вашим объяснением.
Деннис Хаарбринк
3
Добро пожаловать на StackOverflow. Пожалуйста, всегда пишите текст описания к вашему ответу.
Адриан Гейне
6
извините, я начинающий, и мой плохой английский, это очень простой пример кода - для начинающих - commit () rollback () begin (), помещенный в класс DB (например), $ query - не один раз - возможно, $ query0 $ query1 - затем проверить их - я использую этот код, это очень легко понять =)
Гедзберг Алекс
20
Его комментарии ясно показывают пример. Хороший код не должен нуждаться в описании текста. Также вопрос требует простого примера. Мне нравится этот ответ.
Нет
@GedzbergAlex для одного запроса нет необходимости транзакции, просто это сбивает с толку о транзакции. Есть ли причина использовать транзакцию для одного запроса?
ɥʇɹɐʞıɥʇɹɐʞ ouɐɯ
35

Так как это первый результат в Google для «транзакции php mysql», я подумал, что добавлю ответ, который явно демонстрирует, как это сделать с помощью mysqli (поскольку первоначальный автор хотел примеры). Вот упрощенный пример транзакций с PHP / mysqli:

// let's 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, поэтому я не могу это комментировать.

EleventyOne
источник
2
В связи с этим я только что обнаружил, что если вы работаете с таблицами InnoDB, то можно блокировать / разблокировать таблицы при использовании подхода autocomitt () к транзакциям, но это невозможно при использовании подхода begin_transaction (): MySQL документация
EleventyOne
+1 за подробный (и прокомментированный) пример с актуальным кодом mysqli. Спасибо за это. И ваше мнение о блокировке / транзакциях действительно очень интересно.
a.real.human.being
1
Влияет ли «autocommit (FALSE)» на другое соединение в той же базе данных / таблице? Я имею в виду, что если мы откроем две страницы, одна из них установит свое соединение на «autocommit (FALSE)», а другая оставит функцию autocommit, ждет ли она функции фиксации или нет. Я хочу знать, является ли autocommit атрибутом для соединений, а не для базы данных / таблицы. Спасибо
Хамид
2
@Hamid $conn->autocommit(FALSE)в приведенном выше примере влияет только на отдельное соединение - оно не влияет на любые другие соединения с базой данных.
EleventyOne
1
ПРИМЕЧАНИЕ: вместо if (!result), должен делать if (result === false), если запрос способен вернуть действительный результат, который будет иметь значение false или ноль.
ToolmakerSteve
10

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

Динеш
источник
7

При использовании подключения 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's 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's 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блока.

Данила Пятов
источник
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;
        }
    }
Marco
источник
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/

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

Еще один пример процедурного стиля с 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);

источник
1
Несколько странный код, но, по крайней мере, он предполагает использование mysqli_multi_query. Любой, кто заинтересован в этом, должен искать в другом месте Google для более чистого примера.
ToolmakerSteve