Transaction: commit() vs rollBack() - php

I have some queries at one script and I want to execute either all of them or none of them ..! I've searched about that and I figured out I have to use transaction.
Actually I want to use PDO::beginTransaction. Now there is two approaches.
rollback() function
commit() function
So what's the difference between them? Both of them seems identical to me, So when should I use which one?
<?php
$dbh->beginTransaction();
$sth1 = $dbh->exec("DROP TABLE fruit");
$sth2 = $dbh->exec("UPDATE dessert SET name = 'hamburger'");
$sth3 = $dbh->exec("INSERT INTO names(id, name) VALUES (NULL, 'peter')");
// which one?
$dbh->commit();
// or
$dbh->rollBack();
// ??
/* Database connection is now back in autocommit mode */
?>

Both of them seems identical to me
That's wrong. Transaction by definition is Atomic in nature means either it will happen and succeed executing all commands in the group or none at all. If it's successful and you want to persist the change then COMMIT else if any of the statement in the group fails then ROLLBACK to get back to pristine state.
So in your case, you would want to have all the below statement execute successfully and if that then COMMIT to persist the change but if any of the statement fails for any so called reason then it may end up giving a undesired result which you don't want to persist and so ROLLBACK and get back to previous consistent state.
$sth1 = $dbh->exec("DROP TABLE fruit");
$sth2 = $dbh->exec("UPDATE dessert SET name = 'hamburger'");
$sth3 = $dbh->exec("INSERT INTO names(id, name) VALUES (NULL, 'peter')");
Read about Transaction and also see this another post PHP + MySQL transactions examples

You use commit to perform the transaction, and rollback is the opposite, you use rollback when you want to keep all unchanged (for example if you have detected some error during some step of the transaction).

Related

Why does multi_query not work when I use transactions?

Here is an example.
$mysqli = new mysqli("localhost", "root", "123", "temp");
$mysqli->begin_transaction();
$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$test = $mysqli->multi_query($sql1);
$mysqli->commit();
There isn't any error in either of the queries, but when calling commit() the values are not stored in the database. The same works perfectly fine if split into separate queries and executed via query().
$mysqli->multi_query($sql1);
$mysqli->commit(); // This will result in a "Commands out of sync; you can't run this command now" error.
The above is identical to:
$mysqli->multi_query($sql1);
$mysqli->query("commit"); // This will result in a "Commands out of sync; you can't run this command now" error.
Whatever you put in $mysqli->query("...");, it WILL result in a "Commands out of sync" error, even with a simple SELECT 1;
The reason for this error is because ->commit() operation runs a single query (commit;). However, the results of the previous queries have not been read.
When a single query() operation is used, the MySQL server will answer with a response frame that depends on the query statement.
When using multi_query(), the following happens at MySQL communication protocol level:
A "Request Set Option" (as displayed in Wireshark) frame is sent with "multi statements" flag set to ON.
A frame containing the whole string transmitted to multi_query() as request.
MySQL server answers with a response that may contain different resultsets. The same is true when calling a stored procedure.
Solution 1
If you want to use multi_query(), you must have your start transaction / commit operations as part of it:
$mysqli = new mysqli("localhost", "root", "123", "temp");
$sql1 = "start transaction;"; // $mysqli->begin_transaction() is a convenience function for simply doing this.
$sql1 .= "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$sql1 .= "commit;"; // $mysqli->commit() is a convenience function for simply doing this.
$mysqli->multi_query($sql1);
/* As in "Solution 2", if you plan to perform other queries on DB resource
$mysqli after this, you must consume all the resultsets:
// This loop ensures that all resultsets are processed and consumed:
do {
$mysqli->use_result();
}
while ($mysqli->next_result());
*/
Solution 2
$mysqli = new mysqli("localhost", "root", "123", "temp");
$mysqli->begin_transaction();
$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$mysqli->multi_query($sql1);
// This loop ensures that all resultsets are processed and consumed:
do {
$mysqli->use_result();
}
while ($mysqli->next_result());
// Now that all resultsets are processed, a single query `commit;` can happen:
$mysqli->commit();
MySQL Reference: "Commands out of sync".
You shouldn't use multi query. Rewrite your code as follows
$mysqli->begin_transaction();
$mysqli->query("insert into test (Name) values ('pratik5')");
$mysqli->query("insert into test (Name) values ('pratik6')");
$mysqli->commit();
or, for the real-life inserts,
$mysqli->begin_transaction();
$stmt = $mysqli->prepare("insert into test (Name) values (?)");
$stmt->bind_param("s", $name);
$name = 'pratik5';
$stmt->execute();
$name = 'pratik6';
$stmt->execute();
$mysqli->commit();
First thing first, in the example you have shown you should not be using multi_query(). You have two separate statements which should be executed separately. See Your Common Sense's answer
multi_query() should be rarely used. It should be used only in situations when you already have a string composed of multiple queries, which you absolutely trust. Don't ever allow variable input into multi_query()!
Why commit() doesn't work after multi_query()?
Truth be told MySQL does throw an error on commit(), but mysqli is not able to throw the error as exception. Whether it is a bug or a technical limitation I do not know. You can see the error if you check manually $mysqli->error property. You should see an error as follows:
Commands out of sync; you can't run this command now
However, the exception is thrown correctly once you call use_result() or store_result().
$mysqli->multi_query(/* SQLs */);
$mysqli->commit();
$r = $mysqli->use_result(); // Uncaught mysqli_sql_exception: Commands out of sync; you can't run this command now
The reason why MySQL says the commands are out of sync is because you can only execute a single command at a time per each MySQL session. However, multi_query() sends the whole SQL string to MySQL in a single command, and let's MySQL run the queries asynchronously. PHP will initially wait for the first result-set producing non-DML query (not INSERT, UPDATE or DELETE) to finish running before the control is passed back to PHP script. This allows MySQL to run the rest of the SQL on the server and buffer the results, while you can do some other operations in PHP and collect the results later. You can't run another SQL command on the same connection until you iterate over all the results from the previous asynchronous queries.
As pointed out in another answer, commit() will try to execute another command on the same connection. You are getting the out of sync error, because you simply haven't finished processing the multi_query() command.
MySQLi blocking loop
Each asynchronous query should be followed by a blocking loop in mysqli. A blocking loop will iterate over all the executed queries on the server and fetch the results one by one, which you can then process in PHP. Here is an example of such loop:
do {
$result = $mysqli->use_result();
if ($result) {
// process the results here
$result->free();
}
} while ($mysqli->next_result()); // Next result will block and wait for next query to finish
$mysqli->store_result(); // Needed to fetch the error as exception
You must have the blocking loop always, even when you know the queries are not going to produce any result-sets.
Solution
Stay away from multi_query()! Most of the time there's a better way of executing SQL files. If you have your queries separate, then don't concatenate them together, and execute each on their own.
If you really need to use multi_query(), and you would like to wrap it in transaction, you must put the commit() after the blocking loop. All the results need to be iterated over before you can execute the COMMIT; command.
$mysqli->begin_transaction();
$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$test = $mysqli->multi_query($sql1);
// $mysqli->commit();
do {
$result = $mysqli->use_result();
if ($result) {
// process the results here
$result->free();
}
} while ($mysqli->next_result());
$mysqli->store_result(); // To fetch the error as exception
$mysqli->commit();
Of course to see any of the mysqli errors you need to enable exception mode. Simply, put this line before new mysqli():
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

SQL php Rollback does't work

I have the following in php:
try {
// INSERT FETCHED LIST INTO ARCHIVE
$stmt1 = $sql->prepare('INSERT INTO hsarchive (LIST) VALUES (?)');
$stmt1->bind_param("s",$total);
$stmt1->execute();
$stmt2 = $sql->prepare('TRUNCATE TABLE highscore');
$stmt2->execute();
$sql->rollback();
$stmt1->close();
$stmt2->close();
} catch (Exception $e) {
echo "error";
$sql->rollback();
}
Engine is InnoDB and the connection is started like:
$sql = getSQLAccess();
$sql->autocommit(false);
$sql->begin_transaction();
with getSQLAccess returning an object of the type connection with user, pw etc. in it.
No matter how I spin this, the table is truncated and the list is inserted into the archive. I tried switching around where I close the statements, and as you can see I'm currently not even committing, as I'm trying to figure out why the rollback doesnt work.
Anyone?
EDIT: So this would be the way to go, according to best answer:
try {
// INSERT FETCHED LIST INTO ARCHIVE
$stmt = $sql->prepare('INSERT INTO hsarchive (LIST) VALUES (?)');
$stmt->bind_param("s",$total);
$stmt->execute();
$stmt->close();
$stmt = $sql->prepare('DELETE FROM highscore');
$stmt->execute();
$stmt->close();
$sql->commit();
} catch (Exception $e) {
$sql->rollback();
}
DDL in transactions
Since we've figured out that there are no FK constraints to table highscore - then your issue is caused because since MySQL 5.0.3, TRUNCATE table syntax is equivalent to deletion of all rows logically but not physically
If there are no foreign key constraints to this table (your case) which restricts from doing this, MySQL will produce TRUNCATE operation via fast scheme: it will do DROP table + CREATE table. So while logically it's same to deletion of all rows, it's not the same in terms of how operation is maintained.
Why this is the difference? Because MySQL doesn't support DDL in transactions. More precise, such operations can not be rolled back. For MySQL, DDL operations will cause immediate implicit commit. That is why you see that your TRUNCATE statement: first, is committed even if you don't commit; second, rollback has no effect on it.
Solution
If you still need to rollback your operation, then, unfortunately, you'll need to use DELETE syntax instead of TRUNCATE. Unfortunately - because, obviously, DELETE is much slower than TRUNCATE, because rows will be processed one by one.

MySQL autocommit - Inserts are ignored

I have a MySQl database with a few tables, all UTF-8 and MyISAM storage.
In PHP I am parsing an XML file which writes a lot of data to the tables. I am using just simple Insert statements and the mysqli functions.
There not so many read actions on the table and no one of them are during the inserts. First the performance was very very slow so I added SET AUTOCOMMIT = 0 at the beginning of the script.
The issue I have now is that all my inserts which are in e.g. the third foreach loop are ignored and do not appear in the mysql tables. Everything before that is fine.
So my question is, what am I doing wrong and how should I do it?
With autocommit on = Everything is inserted but very very slow
With autocommit off = Everything is very fast but a lot of inserts are ignored
Hopefully someone have an idea and can help.
MySQL is faster with autocommit turned off because INSERTs are not written to your database immediately; the data is only saved when you execute a COMMIT statement. Do you have a COMMIT statement after inserting your data?
You should try like this:
<?php
try {
$db->beginTransaction();
$stmt = $db->prepare("SOME QUERY?");
$stmt->execute(array($value1));
$stmt = $db->prepare("YET ANOTHER QUERY??");
$stmt->execute(array($value2, $value3));
$db->commit();
} catch(PDOException $ex) {
//Something went wrong then rollback!
$db->rollBack();
echo $ex->getMessage();
}
Note: calling bindTransaction() turns off auto commit automatically.
While with mysqli, you can use following instead:
mysqli_autocommit($dbh, FALSE); // turn off auto-commit
mysqli_rollback($dbh); // if error, roll back transaction
mysqli_commit($dbh); // commit transaction

PHP: SQL Prepared statement transaction not working correctly. It's inserting 1 SQL statement, not both

I'm finding that the PDO Transaction is only commiting 1 of my 2 SQL statement. For some reason, my PHP script is not inserting into my MySQL database 'homes' table BUT it does insert into the 'invoices' table - even though I'm using a PHP PDO database transaction.
Code below:
$conn_str = DB . ':host=' . DB_HOST . ';dbname=' . DB_NAME;
$dbh = new PDO($conn_str, DB_USERNAME, DB_PASSWORD);
/* Begin a transaction, turning off autocommit */
$dbh->beginTransaction();
$sql_create_home_listing = 'INSERT INTO homes ( customer_id,
account_type_id,
address,
city,
state,
zip,
display_status
) VALUES (?,?,?,?,?,?,true)';
$stmt = $dbh->prepare($sql_create_home_listing);
$stmt->bindParam(1, $customer_id);
$stmt->bindParam(2, $account_type_id);
$stmt->bindParam(3, $_SESSION['street']);
$stmt->bindParam(4, $_SESSION['city']);
$stmt->bindParam(5, $_SESSION['state']);
$stmt->bindParam(6, $_SESSION['zip']);
$stmt->execute();
$home_id = $dbh->lastInsertId();
// another SQL statement
$sql_create_invoice = "INSERT INTO invoices (customer_id, account_type_id, price, cc_authorized, home_id) VALUES (?,?,?,?,?)";
$cc_authorized = false;
$anotherStmt = $dbh->prepare($sql_create_invoice);
$anotherStmt->bindParam(1, $customer_id);
$anotherStmt->bindParam(2, $account_type_id);
$anotherStmt->bindParam(3, $account_plan_price);
$anotherStmt->bindParam(4, $cc_authorized);
$anotherStmt->bindParam(5, $home_id);
$anotherStmt->execute();
/* Commit the changes */
$dbh->commit();
How is it possible that only the 'invoices' table is getting the insert and not both the 'invoices' table AND the 'homes' table?
Note: no errors are reported by PHP.
Firstly, check whether you really have any errors in PHP - it's notoriously crap at telling you. There is an option you can set on PDO objects to throw an exception on database error - I recommend you set it.
It sounds to me like it is inserting the row, but you're in a transaction which is never committed so it gets rolled back, and the row is never visible (Your isolation mode is READ_COMMITTED or higher).
In that case you need to re-examine how your application uses transactions and try to see if you can get it properly consistent. Using transactions is nontrivial; it needs either a lot of code to get things right, or some well thought out wrapper code or something. If you don't understand any of that, leave autocommit on.
Check that your tables are transactional (InnoDB vs MyISAM as an example..).
Might want to do a try catch, so that if there is an error you can rollback. This may give you some insight.
Is true a valid value for display_status ?
MySQL does not have a bool type, nor does it have true as a predefined function or constant. So my guess is that it's a syntax error.
Get some decent error handling. Set the option which throws when there's a SQL error (See PDO docs!)
I found the problem. On my homes table I had a field deemed as 'unique' but it was not and preventing the insert from happening

Cant the prepared statement be used throught the transaction, from php?

I work on a LAPP environment (linux apache postgresql php), and i'm just triyn to find out how to use the prepared statement within the transaction (if is possible).
I hope code will explain better then words:
Example 1, simple transaction:
BEGIN;
INSERT INTO requests (user_id, description, date) VALUES ('4', 'This dont worth anything', NOW());
UPDATE users SET num_requests = (num_requests + 1) WHERE id = '4';
--something gone wrong, cancel the transaction
ROLLBACK;
UPDATE users SET last_activity = NOW() WHERE id = '4'
COMMIT;
In the example above, if i undestood right the transaction, the only effect in the database will be the last_activity's update... ye?
If i try to use that transaction in php (both with PDO or pg_ methods) the code should look like that (example 2):
/* skip the connection */
pg_query($pgConnection, "BEGIN");
pg_query($pgConnection, "INSERT INTO requests (user_id, description, date) VALUES ('$id_user', 'This dont worth anything', NOW())");
pg_query($pgConnection, "UPDATE users SET num_requests = (num_requests + 1) WHERE id = '$id_user'");
//something gone wrong, cancel the transaction
pg_query($pgConnection, "ROLLBACK");
pg_query($pgConnection, "UPDATE users SET last_activity = NOW() WHERE id = '$id_user'");
pg_query($pgConnection, "COMMIT");
And that works fine. Maybe ugly to see, but seem to work (suggestion are always welcome)
Anyway, my problem come when i try to envolve the example 2 with the prepared statements (i know that in the example 2 the use of prepared statements is not very usefull)
Example 3:
/* skip the connection */
pg_prepare($pgConnection, 'insert_try', "INSERT INTO requests (user_id, description, date) VALUES ('$1', '$2', $3)");
pg_query($pgConnection, "BEGIN");
pg_execute($pgConnection, 'insert_try', array($user_id, 'This dont worth anything', date("Y-m-d")));
/* and so on ...*/
Well, the example 3 simply dont work, the prepared statement will be effective if the transaction due in rollback.
So, the prepared statements can't be used in the transaction, or am i taking the wrong way?
EDIT:
After some try with PDO, i'm arrived at this point:
<?php
$dbh = new PDO('pgsql:host=127.0.0.1;dbname=test', 'myuser', 'xxxxxx');
$rollback = false;
$dbh->beginTransaction();
//create the prepared statements
$insert_order = $dbh->prepare('INSERT INTO h_orders (id, id_customer, date, code) VALUES (?, ?, ?, ?)');
$insert_items = $dbh->prepare('INSERT INTO h_items (id, id_order, descr, price) VALUES (?, ?, ?, ?)');
$delete_order = $dbh->prepare('DELETE FROM p_orders WHERE id = ?');
//move the orders from p_orders to h_orders (history)
$qeOrders = $dbh->query("SELECT id, id_customer, date, code FROM p_orders LIMIT 1");
while($rayOrder = $qeOrders->fetch(PDO::FETCH_ASSOC)){
//h_orders already contain a row with id 293
//lets make the query fail
$insert_order->execute(array('293', $rayOrder['id_customer'], $rayOrder['date'], $rayOrder['code'])) OR var_dump($dbh->errorInfo());
//this is the real execute
//$insert_order->execute(array($rayOrder['id'], $rayOrder['id_customer'], $rayOrder['date'], $rayOrder['code'])) OR die(damnIt('insert_order'));
//for each order, i move the items too
$qeItems = $dbh->query("SELECT id, id_order, descr, price FROM p_items WHERE id_order = '" . $rayOrder['id'] . "'") OR var_dump($dbh->errorInfo());
while($rayItem = $qeItems->fetch(PDO::FETCH_ASSOC)){
$insert_items->execute(array($rayItem['id'], $rayItem['id_order'], $rayItem['descr'], $rayItem['price'])) OR var_dump($dbh->errorInfo());
}
//if everything is ok, delete the order from p_orders
$delete_order->execute(array($rayOrder['id'])) OR var_dump($dbh->errorInfo());
}
//in here i'll use a bool var to see if anythings gone wrong and i need to rollback,
//or all good and commit
$dbh->rollBack();
//$dbh->commit();
?>
The code above fails with this output:
array(3) { [0]=> string(5) "00000" [1]=> int(7) [2]=> string(62) "ERROR: duplicate key violates unique constraint "id_h_orders"" }
array(3) { [0]=> string(5) "25P02" [1]=> int(7) [2]=> string(87) "ERROR: current transaction is aborted, commands ignored until end of transaction block" }
Fatal error: Call to a member function fetch() on a non-object in /srv/www/test-db/test-db-pgsql-08.php on line 23
So, seem like when the first execute fail (the one with id 293) the transaction is automatically aborted... does the PDO auto-rollback, or something else?
My goal is to complete the first big while loop, and at the end, using a bool var as flag, decide if to rollback or commit the transaction.
With PostgreSQL, if any statement produces a server error during a transaction, that transaction is marked as aborted. That doesn't mean it's actually rolled back yet- just that you can hardly do anything except roll it back. I assume PDO doesn't automatically issue a rollback, it waits for you to call the "rollback" method.
To achieve what I think you want, you can use a savepoint. Rather than rolling back the entire transaction, you can just rollback to the savepoint, and continue the transaction. I'll give an example of using this from psql:
srh#srh#[local] =# begin;
BEGIN
srh#srh#[local] *=# insert into t values(9,6,1,true);
INSERT 0 1
srh#srh#[local] *=# savepoint xyzzy;
SAVEPOINT
srh#srh#[local] *=# insert into t values(9,6,2,true);
ERROR: duplicate key value violates unique constraint "t_pkey"
srh#srh#[local] !=# insert into t values(10,6,2,true);
ERROR: current transaction is aborted, commands ignored until end of transaction block
srh#srh#[local] !=# rollback to savepoint xyzzy;
ROLLBACK
srh#srh#[local] *=# insert into t values(10,6,2,true);
INSERT 0 1
srh#srh#[local] *=# commit;
COMMIT
srh#srh#[local] =#
So in this example, the first column of t is the primary key. I tried to insert two rows into t with an id of 9, and the got a uniqueness constraint. I can't just redo the insert with the right values because now any statement will get the "current transaction is aborted..." error. But I can do "rollback to savepoint", which brings me back to the state I was at when I did "savepoint" ("xyzzy" is the name of the savepoint). Then I can issue the correct insert command and finally commit the transaction (which commits both inserts).
So in your case, I suspect what you need to do is create a savepoint before your UPDATE statement: if it gives an error, do a "rollback to savepoint" and set your flag. You'll need to generate unique names for the savepoints: using a counter, for example.
I'm not entirely sure I understand why you're doing all this. Surely you want to stop processing as soon as you know you're going to roll back the transaction? Or is there some other processing going on in the loop that has to happen as well?
You should be using
pdo_obj->beginTransaction()
pdo_obj->commit()
pdo_obj->prepare()
Also you have a random commit at the end of your first example.
begin
// do all your stuff
// check for errors through interface
commit OR not
pg_query($pgConnection, "ROLLBACK"); // end of tx(1)
// start new transaction after last rollback = tx(2)
pg_query($pgConnection, "UPDATE users SET last_activity = NOW() WHERE id = '$id_user'");
// commit tx(2) or don't here
// this isn't needed pg_query($pgConnection, "COMMIT");
If you didn't commit, and need to manually adjust stuff, use another transaction. Preparing your query (if I recall) is part of a transaction, because it can fail. You can't really just manually take an SQL statement and turn it into queries. The PDO interface has abstractions for a reason. :)
http://uk3.php.net/pdo <-- Solid examples of PHP/Postgre using PDO
good luck

Categories