Использование транзакций в PDO на PHP. Введение в транзакции в MySQL Mysql php транзакции пример

(9)

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

И еще один вопрос. Я уже много программировал и не использовал транзакции. Могу ли я поместить функцию PHP или что-то в header.php , если один из mysql_query терпит неудачу, то остальные тоже не 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"); }

Answers

При использовании 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 .

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

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"); }

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

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

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; } }

Поскольку это первый результат в 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, поэтому я не могу прокомментировать это.

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

Думаю, я это понял, верно?

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"); }

Функции mysql_ * были обесценены (как и php 5.5 ), учитывая тот факт, что были разработаны лучшие функции и структуры кода. Тот факт, что функция была обесценена, означает, что больше не будет прилагаться усилий для ее улучшения с точки зрения производительности и безопасности, а это означает , что она менее надежна для будущего .

Если вам нужно больше причин:

  • Функции mysql_ * не поддерживают подготовленные операторы.
  • Функции mysql_ * не поддерживают привязку параметров.
  • Функции mysql_ * не имеют функциональности для объектно-ориентированного программирования.
  • список продолжается...

(9)

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

И еще один вопрос. Я уже много программировал и не использовал транзакции. Могу ли я поместить функцию PHP или что-то в header.php , если один из mysql_query терпит неудачу, то остальные тоже не 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"); }

Answers

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

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; } }

Думаю, я это понял, верно?

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"); }

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

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

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

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

Поскольку это первый результат в 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, поэтому я не могу прокомментировать это.

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

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

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"); }

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

Неустранимая ошибка: допустимый размер памяти из XXX байт исчерпан (пытался выделить XXX байтов)

Недостаточно памяти для запуска вашего скрипта. PHP достиг предела памяти и перестает его выполнять. Эта ошибка является фатальной, сценарий останавливается. Значение предела памяти можно настроить либо в файле php.ini либо с помощью ini_set("memory_limit", "128 M"); в скрипте (который перезапишет значение, определенное в php.ini). Целью ограничения памяти является предотвращение того, чтобы один скрипт PHP собирал всю доступную память и приводил к остановке всего веб-сервера.

Первое, что нужно сделать, это свести к минимуму объем памяти, необходимый вашему сценарию. Например, если вы читаете большой файл в переменной или извлекаете много записей из базы данных и сохраняете их все в массиве, которые могут использовать много памяти. Измените свой код, чтобы вместо этого читать строки по строке или извлекать записи базы данных по одному, не сохраняя их все в памяти. Это требует немного концептуального понимания того, что происходит за кулисами, и когда данные хранятся в памяти и в других местах.

Связанные вопросы:

  • Все «Неустранимая ошибка: допустимый размер памяти из XXX байт исчерпан». Вопросы по

Чтобы начать транзакцию, необходимо выполнить метод «beginTransaction()» у объекта класса «PDO». Рассмотрим пример на php:

$dsn = "mysql:dbname=1;host=localhost"; $user = "root"; $password = ""; $driver = array(PDO:: MYSQL_ATTR_INIT_COMMAND => "SET NAMES `utf8`"); try { $db = new PDO($dsn, $user, $password, $driver); //создаем новый объект класса PDO для взаимодействия с БД } catch (PDOException $e) { echo "Подключение не удалось: ". $e->getCode() ."|". $e->getMessage()); exit(); } $db->beginTransaction(); //Начинаем транзакцию $db->exec("INSERT INTO user VALUES (1, "Коля")"); $db->exec("INSERT INTO user VALUES (2, "Алексей")"); $db->exec("INSERT INTO user VALUES (1, "Иван")"); ... //далее commit() или rollBack()

Чтобы зафиксировать изменения в транзакции, у объекта PDO нужно выполнить метод commit():

$db->commit();

Чтобы отменить изменения (откатить транзакцию), у объекта $db PDO необходимо вызвать метод rollBack():

$db->rollBack();

Обратите внимание, если начать транзакцию и ее не завершить (то есть в рамках скрипта не выполнить ни commit() ни rollback()), то при завершении работы скрипта транзакция откатится автоматически, если не установлено постоянного соединения с БД (не установлен атрибут PDO::ATTR_PERSISTENT => true). Тоже самое произойдет при уничтожении PDO объекта ($db=null) в коде скрипта, в этом случае PDO завершит текущее соединение с БД. Откат транзакции при завершении соединения с БД делает PDO драйвер, это очень удобно при аварийном завершении скриптов.

Транзакции доступны только для таблиц с типом InnoDB. Для MyISAM таблиц транзакции недоступны.

По умолчанию в MySQL включен autocommit. Это означает подтверждение (фиксацию) каждого запроса к БД, это означает, что каждый запрос к базе данных в MySQL по умолчанию является транзакцией. Поэтому вставка данных в таблицы типа InnoDB идет медленнее, чем в таблицы типа MyISAM. При импорте данных и вставке больших объемов информации в таблицы InnoDB следует отключать autocommit и фиксировать изменения, т. е. делать commit не после каждой вставки, а после нескольких вставок (коммитить только после совершения группы запросов).

Обработка ошибок PDO в PHP и откат транзакций при ошибках

По умолчанию в PDO установлен «тихий» режим обработки ошибок (silent mode). Это означает, что при возникновении ошибки в PDO, исключение выброшено не будет и работа скрипта продолжится. Ошибки не будут ловиться с помощью try catch блоков, код ошибки и описание будет возможно получить только с помощью специальных методов у объекта PDO или PDOStatement: errorCode() и errorInfo(). Для того, чтобы ошибки PDO можно было «ловить» в try..catch, нужно изменить режим обработки ошибок с PDO::ERRMODE_SILENT на PDO::ERRMODE_EXCEPTION . Внимание: после установки этого режима желательно обрабатывать исключения при каждом запросе к БД, так как при возникновении ошибки остановится работа скрипта и произойдет остановка всего web-приложения. Если вы устанавливайте этот режим, обязательно используйте try..catch блоки в каждом запросе, чтобы ловить ошибки.

Рассмотрим, как изменится логика приложения после включения режима обработки ошибок PDO::ERRMODE_EXCEPTION:

$dsn = "mysql:dbname=1;host=localhost";$user = "root";$password = ""; $driver = array(PDO:: MYSQL_ATTR_INIT_COMMAND => "SET NAMES `utf8`"); try { $db = new PDO($dsn, $user, $password, $driver); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //Устанавливаем режим обработки ошибок ERRMODE_EXCEPTION } catch (PDOException $e) { echo "Подключение не удалось: ". $e->getCode() ."|". $e->getMessage(); exit(); } try { $db->beginTransaction(); //Начинаем транзакцию $db->exec("INSERT INTO user VALUES (1, "Коля")"); $db->exec("INSERT INTO user VALUES (2, "Алексей")"); $db->exec("INSERT INTO user VALUES (1, "Иван")"); catch (PDOException $e) { //Ловим ошибку $db->rollBack(); echo "PDOException: ".$e->getCode() ."|". $e->getMessage()); exit(); } $db->commit(); //Если все запросы прошли успешно - коммитим

Это простой пример обработки ошибок при использовании транзакций. В реальных приложениях нужно обязательно смотреть код ошибки. Если, например, это отключение от MySQL сервера, то совсем необязательно завершать работу скрипта после отката транзакции. В этом случае можно попытаться переподключиться к SQL серверу через какой то промежуток времени и пробовать заново выполнить текущий запрос или транзакцию. Если это, например, ошибка несовпадения типа данных — то в этом случае конечно нет смысла повторять запрос, можно откатывать транзакцию и завершить работу скрипта. При возникновении определенных ошибок вообще можно не откатывать транзакцию. Вообщем надо смотреть код SQL ошибки — и уже посмотрев решать как дальше поступать.

Рассмотрим пример:

Function connect_db() { $dsn = "mysql:dbname=1;host=localhost"; $user = "root"; $password = ""; $driver = array(PDO:: MYSQL_ATTR_INIT_COMMAND => "SET NAMES `utf8`"); try { $db = new PDO($dsn, $user, $password, $driver); //создаем новый объект класса PDO для взаимодействия с БД $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //Устанавливаем режим обработки ошибок ERRMODE_EXCEPTION } catch (PDOException $e) { echo "Подключение не удалось: ". $e->getCode() ."|". $e->getMessage()); return false; } return $db; } function doQuery($db, $sql, $count_db = 0) { if($count_db>5) { echo "Кол-во попыток подключения превысило допустимый лимит"; return false; } try { if($db->inTransaction()) { echo "Транзакция уже начата"; return false; } $db->beginTransaction();//Начинаем транзакцию $db->exec($sql); } catch (PDOException $e) { if($db->inTransaction()) $db->rollBack(); if($e->errorInfo >= 2000&&$db=connect_db()) { //если код ошибки > 2000 (это потеря соединения с БД и пр.) то пробуем переподключится и выполнить запрос заново return doQuery($db, $sql, $count_db++); } else { echo "PDOException: ".$e->getCode() ."|". $e->getMessage(); return false; } } if($db->inTransaction()) return $db->commit(); }

Обратите внимание на метод:

$db->inTransaction();

Он проверяет, начата ли транзакция или нет. Это очень важно, так как если вызвать метод beginTransaction() в том случае, если транзакция уже начата, или наоборот вызвать метод rollBack() или commit() когда транзакция не еще начата, то в любом из этих случаев вы получите ФАТАЛЬНУЮ ошибку. Да, поэтому всегда проверяйте начата ли транзакция, прежде чем ее завершить, в противном случае вы просто словите ошибку и ваше приложение аварийно завершится.

Транзакция — это операция, состоящая из одного или нескольких запросов к базе данных. Суть транзакций — обеспечить корректное выполнение всех запросов в рамках одной транзакции, а так-же обеспечить механизм изоляции транзакций друг от друга для решения проблемы совместного доступа к данным.

Любая транзакция либо выполняется полностью, либо не выполняется вообще.

В транзакционной модели есть два фундаментальных понятия: COMMIT и ROLLBACK. COMMIT означает фиксацию всех изменений в транзакции. ROLLBACK означает отмену (откат) изменений, произошедших в транзакции.

При старте транзакции все последующие изменения сохраняются во временном хранилище. В случае выполнения COMMIT, все изменения, выполненные в рамках одной транзакции, сохранятся в физическую БД. В случае выполнения ROLLBACK произойдет откат и все изменения, выполненные в рамках этой транзакции, не сохранятся.

В MySQL транзакции поддерживаются только таблицами innoDB. Таблицы MyISAM транзакции не поддерживают. В innoDB по умолчанию включен autocommit, это значит, что по умолчанию каждый запрос эквивалентен одной транзакции.

Транзакция начинается со специального запроса «START TRANSACTION», либо «BEGIN». Чтобы закончить транзакцию, нужно либо зафиксировать изменения (запрос COMMIT), либо откатить их (запрос ROLLBACK).

Пример с COMMIT:

Start transaction; (также, можно написать BEGIN ;) …какие-то действий с БД (insert, update,delete…) commit; //Фиксация действий, запись их в физическую БД

Пример с ROLLBACK:

Set autocommit=0; //отключаем autocommit Start transaction; …какие-то действия с БД (insert, update,delete…) rollback; // отменяем серию действий, не производим запись в физическую БД

В MySQL не существует механизма вложенных транзакций. Одно соединение с БД — одна транзакция. Новая транзакция в пределах одного соединения может начаться только после завершения предыдущей.

Для некоторых операторов нельзя выполнить откат с помощью ROLLBACK. Это операторы языка определения данных (Data Definition Language — DDL). Сюда входят запросы CREATE, ALTER, DROP, TRUNCATE, COMMENT, RENAME.

Следующие операторы неявно завершают транзакцию (как если бы перед их выпол­нением был выдан COMMIT):

  • ALTER TABLE
  • DROP DATABASE
  • LOAD MASTER DATA
  • SET AUTOCOMMIT = 1
  • BEGIN
  • DROP INDEX
  • LOCK TABLES
  • START TRANSACTION
  • CREATE INDEX
  • DROP TABLE
  • RENAME TABLE
  • TRUNCATE TABLE

Обратите внимание, что в случае SQL ошибки, транзакция сама по себе не откатится. Обычно ошибки обрабатываются уже с помощью sql wrapper’ов в самом приложении, таких как PHP PDO например. Если вы захотите откатывать изменения в случае ошибки прямо в MySQL, можно создать специальную процедуру и уже в ней выполнять ROLLBACK в обработчике:

CREATE PROCEDURE prc_test () BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK ; //Вот здесь откатываем транзакцию в случае ошибки END ; START TRANSACTION ; INSERT INTO tmp_table VALUES ("null" ) ; COMMIT ; END ; CALL prc_test () ;

Но этот способ скорее просто для ознакомления, а не руководство к действию. Почему? Я крайне не рекомендую так поступать, так как в основном ошибки базы данных обрабатываются с помощью SQL оберток на стороне приложения, таких как PHP PDO например, чтобы оттуда полностью управлять транзакциями.

Рассмотрим практический пример: есть 2 таблицы, пользователи — users и информация о пользователях — user_info. Представим, что нам нужно либо выполнить 3 запроса к базе данных, либо не выполнять их вообще, так как иначе это приведет к сбоям в работе приложения.

Start transaction; INSERT INTO user (id, nik) VALUES (1, "nikola"); INSERT INTO user_info (id, id_user, item_name, item_value) VALUES (1, 1, "Имя", "Николай"); INSERT INTO user_info (id, id_user, item_name, item_value) VALUES (2, 1, "Возраст", "24"); commit;

В целом я думаю принцип работы транзакции понятен. Но все не так просто. Существуют проблемы параллельных транзакций. Рассмотрим пример. Представим, что во время выполнения этой транзакции, другой пользователь создал вторую параллельную транзакцию и сделал запрос SELECT * FROM user после того, как в нашей транзакции был выполнен первый запрос «INSERT INTO user (id, nik) VALUES (1, ‘nikola’)». Что увидит пользователь второй транзакции? Сможет ли он увидеть вставленную запись даже тогда, когда результаты первой транзакции еще не зафиксировались (не произошел COMMIT)? Или он сможет увидеть изменения только после того, как результаты первой транзакции будут зафиксированы? Оказывается имеют место быть оба варианта. Все зависит от уровня изоляции транзакции.

У транзакций есть 4 уровня изоляции:

  • 0 - Чтение неподтверждённых данных (грязное чтение) (Read Uncommitted, Dirty Read) - самый низкий уровень изоляции. При этом уровне возможно чтение незафиксированных изменений параллельных транзакций. Как раз в этом случае второй пользователь увидит вставленную запись из первой незафиксированной транзакции. Нет гарантии, что незафиксированная транзакция будет в любой момент откачена, поэтому такое чтение является потенциальным источником ошибок.
  • 1 - Чтение подтверждённых данных (Read Committed) - здесь возможно чтение данных только зафиксированных транзакций. Но на этом уровне существуют две проблемы. В этом режиме строки, которые участвуют в выборке в рамках транзакции, для других параллельных транзакций не блокируются, из этого вытекает проблема № 1: «Неповторяемое чтение» (non-repeatable read) — это ситуация, когда в рамках транзакции происходит несколько выборок (SELECT) по одним и тем же критериям, и между этими выборками совершается параллельная транзакция, которая изменяет данные, участвующие в этих выборках. Так как параллельная транзакция изменила данные, результат при следующей выборке по тем же критериям в первой транзакции будет другой. Проблема № 2 — «Фантомное чтение» — этот случай рассмотрен ниже.
  • 2 - Повторяемое чтение (Repeatable Read, Snapshot) - на этом уровне изоляции так же возможно чтение данных только зафиксированных транзакций. Так же на этом уровне отсутствует проблема «Неповторяемого чтения», то есть строки, которые участвуют в выборке в рамках транзакции, блокируются и не могут быть изменены другими параллельными транзакциями. Но таблицы целиком не блокируются. Из-за этого остается проблема «фантомного чтения». «Фантомное чтение» — это когда за время выполнения одной транзакции результат одних и тех же выборок может меняться по причине того, что блокируется не вся таблица, а только те строки, которые участвуют в выборке. Это означает, что параллельные транзакции могут вставлять строки в таблицу, в которой совершается выборка, поэтому два запроса SELECT * FROM table могут дать разный результат в разное время при вставке данных параллельными транзакциями.
  • 3 - Сериализуемый (Serializable) - сериализуемые транзакции. Самый надежный уровень изоляции транзакций, но и при этом самый медленный. На этом уровне вообще отсутствуют какие либо проблемы параллельных транзакций, но за это придется платить быстродействием системы, а быстродействие в большинстве случаев крайне важно.

По умолчанию в MySQL установлен уровень изоляции № 2 (Repeatable Read). И, как я считаю, разработчики MySQL не зря сделали по умолчанию именно этот уровень, так как он наиболее удачный для большинства случаев. С первого раза может показаться, что самый лучший вариант № 3 — он самый надежный, но на практике вы можете испытать большие неудобства из-за очень медленной работы вашего приложения. Помните, что многое зависит не от того, насколько хорош уровень изоляции транзакций в БД, а от того, как спроектировано ваше приложение. При грамотном программировании, можно даже использовать самый низкий уровень изоляции транзакций — все зависит от особенностей структуры и грамотности разработки вашего приложения. Но ненужно стремиться к самому низкому уровню изоляции — нет, просто если вы используйте не самый защищенный режим, следует помнить о проблемах параллельных транзакций, в этом случае вы не растеряетесь и все сделайте правильно.

SET TRANSACTION — этот оператор устанавливает уровень изоляции следующей транзакции, глобально либо только для текущего сеанса.

  • SET TRANSACTION ISOLATION LEVEL
    { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }

Существующие соединения не затрагиваются. Для выполнения этого оператора нужно иметь привилегию SUPER. Применение ключевого слова SESSION уста­навливает уровень изоляции по умолчанию всех будущих транзакций только для теку­щего сеанса.

Вы можете также установить начальный глобальный уровень изоляции для сервера mysqld, запустив его с опцией -transaction-isolation

Now that you"re connected via PDO, you must understand how PDO manages transactions before you start issuing queries. If you"ve never encountered transactions before, they offer 4 major features: Atomicity, Consistency, Isolation and Durability (ACID). In layman"s terms, any work carried out in a transaction, even if it is carried out in stages, is guaranteed to be applied to the database safely, and without interference from other connections, when it is committed. Transactional work can also be automatically undone at your request (provided you haven"t already committed it), which makes error handling in your scripts easier.

Transactions are typically implemented by "saving-up" your batch of changes to be applied all at once; this has the nice side effect of drastically improving the efficiency of those updates. In other words, transactions can make your scripts faster and potentially more robust (you still need to use them correctly to reap that benefit).

Unfortunately, not every database supports transactions, so PDO needs to run in what is known as "auto-commit" mode when you first open the connection. Auto-commit mode means that every query that you run has its own implicit transaction, if the database supports it, or no transaction if the database doesn"t support transactions. If you need a transaction, you must use the PDO::beginTransaction() method to initiate one. If the underlying driver does not support transactions, a PDOException will be thrown (regardless of your error handling settings: this is always a serious error condition). Once you are in a transaction, you may use PDO::commit() or PDO::rollBack() to finish it, depending on the success of the code you run during the transaction.

Warning

PDO only checks for transaction capabilities on driver level. If certain runtime conditions mean that transactions are unavailable, PDO::beginTransaction() will still return TRUE without error if the database server accepts the request to start a transaction.

An example of this would be trying to use transactions on MyISAM tables on a MySQL database.

When the script ends or when a connection is about to be closed, if you have an outstanding transaction, PDO will automatically roll it back. This is a safety measure to help avoid inconsistency in the cases where the script terminates unexpectedly--if you didn"t explicitly commit the transaction, then it is assumed that something went awry, so the rollback is performed for the safety of your data.

Warning

The automatic rollback only happens if you initiate the transaction via PDO::beginTransaction() . If you manually issue a query that begins a transaction PDO has no way of knowing about it and thus cannot roll it back if something bad happens.

Example #1 Executing a batch in a transaction

In the following sample, let"s assume that we are creating a set of entries for a new employee, who has been assigned an ID number of 23. In addition to entering the basic data for that person, we also need to record their salary. It"s pretty simple to make two separate updates, but by enclosing them within the PDO::beginTransaction() and PDO::commit() calls, we are guaranteeing that no one else will be able to see those changes until they are complete. If something goes wrong, the catch block rolls back all changes made since the transaction was started, and then prints out an error message.

try {
$dbh = new PDO ("odbc:SAMPLE" , "db2inst1" , "ibmdb2" ,
array(PDO :: ATTR_PERSISTENT => true ));
echo "Connected\n" ;
} catch (Exception $e ) {
die("Unable to connect: " . $e -> getMessage ());
}

try {
$dbh -> setAttribute (PDO :: ATTR_ERRMODE , PDO :: ERRMODE_EXCEPTION );

$dbh -> beginTransaction ();
$dbh -> exec ("insert into staff (id, first, last) values (23, "Joe", "Bloggs")" );
$dbh -> exec ("insert into salarychange (id, amount, changedate)
values (23, 50000, NOW())"
);
$dbh -> commit ();

} catch (Exception $e ) {
$dbh -> rollBack ();
echo "Failed: " . $e -> getMessage ();
}
?>

You"re not limited to making updates in a transaction; you can also issue complex queries to extract data, and possibly use that information to build up more updates and queries; while the transaction is active, you are guaranteed that no one else can make changes while you are in the middle of your work. For further reading on transactions, refer to the documentation provided by your database server.

КАТЕГОРИИ

ПОПУЛЯРНЫЕ СТАТЬИ

© 2024 «minomin.ru» — Сайт о компьютерах, и работе в интернете