Why the following PDO transaction is committing? - php

I think the question itself is pretty self-explanatory. The code is given below -
<?php
$PDO = NULL;
$pdo_dsn = 'mysql:host=localhost;dbname=pdo_test';
$pdo_persistence = array( PDO::ATTR_PERSISTENT => true );
$db_user = 'root';
$db_pass = '';
$db_query = "INSERT INTO person(name, address)
VALUES ('Mamsi Mamsi', 'Katabon')";
try
{
$PDO = new PDO($pdo_dsn, $db_user, $db_pass,
$pdo_persistence);
}
catch(PDOException $e)
{
echo "Error occured: ". $e->getMessage();
die();
}
$PDO->setAttribute(PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION);
$PDO->setAttribute(PDO::ATTR_AUTOCOMMIT, false);
try
{
$PDO->beginTransaction();
$PDO->exec($db_query);
throw new PDOException('Generated Exception');
$PDO->commit();
}
catch(PDOException $e)
{
echo "An error occured while doing a database transaction. The
error message is : ".$e->getMessage();
$PDO->rollBack();
die();
}
?>
Even if I am rolling back the transaction inside the catch block, data are still being inserted into the database. Why?
EDIT
I am adding the following few lines from the documentation for further clarification -
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.
Also, the following lines from this page -
bool PDO::beginTransaction ( void )
Turns off autocommit mode. While autocommit mode is turned off,
changes made to the database via the PDO object instance are not
committed until you end the transaction by calling PDO::commit().
Calling PDO::rollBack() will roll back all changes to the database
and return the connection to autocommit mode.
Some databases, including MySQL, automatically issue an implicit
COMMIT when a database definition language (DDL) statement such as
DROP TABLE or CREATE TABLE is issued within a transaction. The
implicit COMMIT will prevent you from rolling back any other changes
within the transaction boundary.

You should check that you are using INNODB as your database type. MyISAM does not support transactions.

Related

What is the benefit of using "begin_transaction" method in MySQLi?

I'm looking into rollback management with MySQLi, and I'm curious what is the benefit of begin_transaction() method. Many examples I look at skip it entirely by turning autocommit off, then executing some queries with success value returned, and testing a compound Boolean based on the return values to commit or rollback the multiple statements.
It doesn't seem like the begin_transaction() method actually does any useful work in a scenario where we are looking to commit or rollback a group of queries based on the success of all of them. I can see that it adds readability to the code perhaps by explicitly declaring a transaction, but is there a value to begin_transaction() other than in readability? What real work does it do?
As already mentioned in other answers, begin_transaction() starts a transaction, without affecting the value of autocommit. The queries are not commited into the database until you call commit() or trigger an implicit commit.
As described in MySQL docs:
A session that has autocommit enabled can perform a multiple-statement transaction by starting it with an explicit START TRANSACTION or BEGIN statement and ending it with a COMMIT or ROLLBACK statement. See Section 13.3.1, “START TRANSACTION, COMMIT, and ROLLBACK Statements”.
If autocommit mode is disabled within a session with SET autocommit = 0, the session always has a transaction open. A COMMIT or ROLLBACK statement ends the current transaction and a new one starts.
This means that the main difference between begin_transaction() and autocommit(FALSE); is whether you want a one-time transaction or whether you want contiguous transactions.
A simple one-time transaction using begin_transaction() would look like this:
<?php
// Switch on error reporting with exception mode
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'username', 'password', 'db_name');
$mysqli->set_charset('utf8mb4');
try {
// Start transaction
$mysqli->begin_transaction();
$mysqli->query('INSERT INTO some_table(col2) VALUE(4)');
$mysqli->query('INSERT INTO some_table(col2) VALUE(4');
// Commit changes
$mysqli->commit();
} catch (\Throwable $e) {
// Something went wrong. Rollback
$mysqli->rollback();
throw $e;
}
Such approach is clearer than switching the autocommit mode off altogether. .
Only thing that begin_transaction does in contrast with autocommit off is that it does not mess up with autocommit value, so after you commit/rollback transaction stared with begin_transaction the autocommit will be the same as it was before.
I always get tripped up using mysqli transactions in PHP because many examples that cover this topic throw in the method of turning off autocommit. While that works, it's not the same as using mysqli transactions. At least, not in the traditional sense where one would normally key in 'START TRANSACTION' in the query. Here is an example of using a mysqli transaction in PHP without messing with the autocommit setting. If anything, this is a reminder for myself if I ever forget how to do this.
$dbo->begin_transaction();
//Below are the 2 sample queries we need to run; each one will insert a row into a separate table
$result1 = $dbo->query($query1);
$result2 = $dbo->query($query2);
//If both queries were successful, commit
if ($result1 && $result2)
{
$dbo->commit();
}
//Else, rollback and throw error
else
{
$dbo->rollback();
echo 'Writing to DB failed.';
}
As others mention, it is somewhat influenced by your preference. With begin_transaction, you don't have to deal with toggling autocommit before and after your queries.

Calling SQL rollback on php exception

Consider the following scenario:
I open a new connection to MySQL, using mysqli extension
I start a transaction
I do some queries on the MySQL database and to some php computation
Either MySQL or php may throw some exceptions
I commit the transaction
(pseudo) code:
$sql = new mysqli();
...
{ //some scope
$sql->query("START TRANSACTION");
...
if (something)
throw Exception("Something went wrong");
...
$sql->query("COMMIT"); // or $sql->commit()
} //leave scope either normally or through exception
...
$sql->query( ... ); // other stuff not in transaction
If all the above is in some big try catch block (or perhaps even the exception is uncaught) the transaction will remain unfinished. If I then try to do some more work on the database, those actions will belong to the transaction.
My question is: is there some possible mechanism that would permit me to automatically send $sql->rollback() when I leave the transaction-creating scope in an abnormal way?
Some notes:
mysqli::autocommit is not the same as a transaction. It is just a setting for autocommit you turn on or off. Equivalent to MySQL SET autocommit.
mysqli::begin_transaction is not documented and I don't know what is its precise behavior. Will it call rollback when the mysqli object dies for example?
The Command mysqli::begin_transaction is the same (object oriented way) as your $sql->query("START TRANSACTION");;
There is no way, to auto rollback on exception.
You can only comit, if everything has success. Then it will be a "auto rollback" if not. But this way, you will have trouble very soon, if you have more then one commit.
So your current code is allready very good. I would do it the full OOP way:
$sql->begin_transaction();
try {
$sql->query('DO SOMETHING');
if(!true) {
throw new \Exception("Something went wrong");
}
$sql->commit();
}
catch (\Exception exception) {
$sql->rollback();
}
You also can write your own Exception:
class SqlAutoRollbackException extends \Exception {
function __construct($msg, Sql $sql) {
$sql->rollback();
parent::__construct($msg);
}
}
But you still need to catch it.
You cannot automatically rollback on exception, but with a little code you can do what you want. Even if you already are in a try-catch block you can nest them for your transaction section, such as this:
try {
$sql->query("START TRANSACTION");
...
if (something)
throw new PossiblyCustomException("Something went wrong");
...
$sql->query("COMMIT");
} catch (Exception $e) { //catch all exceptions
$sql->query("ROLLBACK");
throw $e; //rethrow the very same exception object
}
Even if you use the most generic catch Exception, the actual type of the exception is known. When you rethrow, it can still be caught by PossiblyCustomException later. Thus, all the handling you already have remains unaffected by this new try-catch block.
So you want to either commit the transaction or rollback the transaction if there was a problem, and there are many ways that a problem could occur - either through a mysql error or php throwing an exception. So why not set up a flag at the start of the transaction, set that flag when the transaction ready to be committed, and check the state of the flag when the process is done to see if a rollback is needed?
If you are afraid of globally scoped variables you could use an overly complicated Singleton class or some kind of static variable for this, but here is the basic idea:
function main() {
global $commit_flag = false;
start_transaction();
doEverything();
if ($commit_flag) {
commit_transaction();
} else {
rollback_transaction();
}
}
function doEverything() {
global $commit_flag;
try {
/* do some stuff that may cause errors */
$commit_flag = true;
} catch { ... }
}
You can handle mysql exceptions using DECLARE ... HANDLER Syntax, but I don't find it proper to try to handle them through PHP !
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO Users ( user_id ) VALUES('1');
INSERT INTO Users ( user_id ) VALUES('2');
COMMIT;
END;
More information can be found here :
http://dev.mysql.com/doc/refman/5.5/en/declare-handler.html

PDO update doesn't work, no errors

I'm staring myself blind on the fact why I can't execute this update statement.
$prepare = 'UPDATE plugins SET status=? WHERE name=?';
$execute = array("0", "testplugin");
$statement = $this->_dbConnectionInstance->prepare($prepare);
$statement->execute($execute);
I set error reporting to PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION and it produces no errors. However, the row in the database is not changed after execution. Executing the query directly on the database (PHPMyAdmin, replacing the question marks with the values) does work, strangely enough.
Also, the database connection is okay (a simple query 'SELECT * FROM plugins' does work).
I used an online available database class (PDO-Quick), which has an architectural error in it. When instantiating the database class, a transaction is started (1). But, nowhere in the class is committed and the transaction stays always 'open'. I now begin a transaction before every query, commit it after execution and rollback when any error occurs (2).
(1)
$this->_dbConnectionInstance->beginTransaction();
(2)
try {
$this->_dbConnectionInstance->beginTransaction();
$stmt = $this->_dbConnectionInstance->prepare($prepare);
$stmt->execute($execute);
$this->_dbConnectionInstance->commit();
} catch(PDOException $e) {
$this->_dbConnectionInstance->rollBack();
exit();
}

MySQL autocommit vs. begin_transaction

This is my (with little pseudo-) code:
$this->db = new mysqli ( $host, $user, $dbpw, $database );
try {
$this->db->mysqli->autocommit(FALSE);
$sql = 'SOME SQL STATEMENT';
$this->db->execute( $sql );
$this->db->mysqli->commit();
}
catch (Exception $e)
{
echo($e);
$this->db->mysqli->rollback();
}
db-class has some enhancements i need but is capable of using the complete mysqli-class.
i have 2 questions.
a. why cant i start the transaction with
$this->db->mysqli->begin_transaction();
its throwing unknown method error. i really dont understand that.
b. do i have to use
$this->db->mysqli->autocommit(TRUE);
?
a. Because mysqli::begin_transaction appeared in PHP 5.5.0. Your PHP version is lower it seems.
b. Depends on your requirements. Enabling autocommit will make MySQL commit transaction automatically and begin another one after executing each SQL statement.

Uncaught exception 'PDOException' with message 'There is no active transaction'?

This is the code i use to insert record. Whenever there is insert error , The subscriber table auto - inc number will still increase even i have roll back? What is the problem? I just want the auto increment number not add when error occur.thanks a lot for help .
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE);
$conn->beginTransaction();
try {
$email = $_POST['Email'];
$FirstName = $_POST['FirstName'];
$LastName = $_POST['LastName'];
$query="INSERT INTO subscriber (Email,FirstName,LastName,CreateDate) VALUES (?,?,?,CURDATE())";
$stmt = $conn->prepare($query);
$stmt->bindParam(1, $email , PDO::PARAM_STR);
$stmt->bindParam(2, $FirstName, PDO::PARAM_STR);
$stmt->bindParam(3, $LastName, PDO::PARAM_STR);
$stmt->execute();
$conn->commit();
}
catch(PDOException $e)
{
$conn->rollBack();
die ($e->getMessage()."<a href='addSub.php'>Back</a>");
}
$conn->beginTransaction();
try {
$userID = $_SESSION['username'];
$query="INSERT INTO list_sub (SubID,ListID) VALUES ('',$_SESSION[ListID])";
$stmt = $conn->prepare($query);
$stmt->execute();
$conn->commit();
}
catch(PDOException $e)
{
$conn->rollBack();
die ($e->getMessage()."<a href='addSub.php'>Back</a>");
}
$conn = null;}
Without knowing line numbers in your code, its hard to know but you commit your transaction at the end of the first try-catch block, and then proceed without starting a new transaction in your second try-catch block.
Add $conn->beginTransaction(); at the beginning of your second try-catch block.
EDIT -
You mention "I just want the auto increment number not add when error occur". You should not rely on the auto-increment feature to generate a "gapless" sequence of numbers.
PDO's auto-commit is probably enabled, and it's causing a problem when you try to rollback, since it has already committed. You can use PDO::ATTR_AUTOCOMMIT to disable this behavior:
$conn->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE);
When running under apache (or perhaps other mechanism that does connection pooling), you may need to check and ensure that you have not got persistent connections in use.
This is because when connection pooling is in effect, you cannot guarantee that the call to ->commit() winds up on the same connection on which you called ->beginTransaction().
This kind of situation manifests in cases where you are using multiple database handles to perform multiple queries, and only one series of queries belong in a transaction, which you have dutifully bound to a specific database handle (variable). However, underneath you, the pooling connection manager may be sending your queries among any available connection so long as you haven't specified "exclusive" use by disabling persistence on one of the handles.
In my case, I was performing a transaction with a series of steps. Due to the code being composed of functions to accomplish specific tasks in the transaction, some of the functions would spin-off a separate database connection to perform some related query (e.g verify ownership of an asset identified in a table row). Running in an apache environment, I would get this error 'There is no active transaction', and in addition, the rollBack() call appeared to be ineffective -- the changes were committed even thought he call to commit reported an error.
Took me a while to narrow it down to persistent connections. Turning off connection pooling on the handle for which I was performing a transaction made things work the way I expected.

Categories