Приклади транзакцій 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, ось хороший підручник .
— Naftali aka Neal

6
Чи "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 може це зробити, залежно від налаштування
    • Подивитися PDO::setAttribute
    • і PDO::ATTR_ERRMODEіPDO::ERRMODE_EXCEPTION
  • інакше, за допомогою іншого API, можливо, доведеться перевірити результат функції, яка використовується для виконання запиту, і викинути виключення самостійно.


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

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


35
Будьте обережні, якщо ви робите операції, які можуть кидати винятки, крім db. Якщо це так, виняток з не-db-оператора може призвести до ненавмисного відкоту (навіть якщо всі виклики db успішні). Як правило, ви вважаєте, що відкат назад - хороша ідея, навіть якщо помилка не була на стороні db, але є випадки, що сторонній / некритичний код може спричинити не так важливі винятки, і ви все одно хочете продовжувати транзакція.
— Халіль Езгюр

6
Який $dbтут тип? mysqli?
— Джейк

3
@Jake Дивіться мою відповідь на прикладі, що використовує mysqli (схожий за стилем на підхід Паскаля).
— EleventyOne

2
його можна легко змінити, щоб зафіксувати PDOExceptionта навіть перевірити значення виключень, якщо це необхідно. us2.php.net/PDOException
— Яміко

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
не потрібно встановлювати автокомісію = 0. транзакції завжди працюють таким чином.
— bgcode

2
@babonk - не впевнений, що це стосується InnoDB?
— buggedcom

6
Я думаю, що коли ви починаєте транзакцію, вона працює так, як якщо б AUTOCOMMIT = 0
— bgcode

4
@babonk має рацію. Після запуску транзакції AUTOCOMMIT = 0 встановлюється неявно, і після закінчення транзакції або через фіксацію, або відкат, MySql повертає значення AUTOCOMMIT, яке було використано до початку транзакції. ПРИМІТКА: НЕ слід встановлювати AUTOCOMMIT = 0, оскільки після внесення змін, якщо ви вирішили вставити / оновити інший рядок, ви повинні це зробити явно.
— eroteev

4
Магазин двигунів повинен бути InnoDB, а не MyISAM!
— javad

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
Вибачте, я беггінер, і моя погана англійська мова, її дуже легкий іспит на код - для беггінерів - фіксують () відкат () починають () ставлять у клас DB (наприклад), $ запит - не один раз - можливо, $ запит0 $ запит1 - тоді чек їх - я використовую цей код, це дуже легко зрозуміти =)
— Гедзберг Алекс

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, тому я не можу це коментувати.


2
У відповідній примітці я щойно виявив, що якщо ви працюєте з таблицями InnoDB, можливо блокувати / розблокувати таблиці при використанні підходу autocomitt () до транзакцій, але це НЕ можливо при використанні підходу start_transaction (): MySQL документація
— EleventyOne

+1 для детального (та коментованого) прикладу з фактичним кодом mysqli. Дякую за це І ваша думка щодо блокування / транзакцій насправді дуже цікава.
— a.real.human.березня

1
Чи вплине "автокомісія (FALSE)" на інше з'єднання в тій же базі даних / таблиці? Я маю на увазі, якщо ми відкриємо дві сторінки, щоб одна з них встановила своє з'єднання на "autocommit (FALSE)", але інша залишила функцію autocommit, чи чекає вона функцію фіксації чи ні. Я хочу знати, чи автокомісія є атрибутом для з'єднань, а не для бази даних / таблиці. Спасибі
— Хамід

2
@Hamid $conn->autocommit(FALSE)у наведеному вище прикладі впливає лише на індивідуальне з'єднання - це не впливає на будь-які інші з'єднання з базою даних.
— EleventyOne

1
ПРИМІТКА: замість цього if (!result)слід робити if (result === false), якщо запит здатний повернути дійсний результат, який би оцінювався як false або zero.
— 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');
});

Таким чином код управління транзакціями не дублюється в рамках проекту. Що добре, тому що, судячи з інших відповідей, визначених ПДО в цій темі, легко помилитися в ньому. Найпоширеніші з них - це забути повторно скинути виняток і розпочати транзакцію всередині 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;
        }
    }

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/transaction/


Неправильний код. Тригер_error поверне справжнє значення (якщо тільки ви не накрутили ваш дзвінок), тому $ результат завжди буде істинним, тому цей код буде пропускати будь-який невдалий запит і завжди намагатиметься здійснити. Настільки ж тривожно, ви використовуєте старий застарілий mysql_query, а не використовуючи mysqli, хоча ви посилаєтесь на підручник, який використовує mysqli. IMHO, вам слід або видалити цей поганий приклад, або замінити його на використання коду, написаного в підручнику 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.