Приклади транзакцій 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.