Will try to make this as simple as possible. I'm using throw/catch with my functions. The function takes in a name a description, and an array of users. It adds the name and description to one table, then takes the array of users, and does a seperate function for adding them into a connector table. I set the function up to turn autocommit off until the last user is entered and then commit. The problem is that if one of the users fails to go in (due to a foreign key constraint), the transaction isn't backed out. Does the fact that I'm calling a seperate function "reset" the autocommit and cause it to not work as intended? Am I doing it wrong by putting the rollback in the Catch clause?
try
{
autocommit=0
run insert query
if query fails: throw error, rollback
else
for count of array
run another function (this function does more SQL and throws its own errors if it fails)
autocommit=1
}
catch
{
rollback, autocommit=1;
display error
}
Hopefully I haven't oversimplified the code. Any help would be appreciated.
$this->dbh->beginTransaction();
try {
// do stuff
$this->dbh->commit();
} catch (PDOException $e) {
$this->dbh->rollBack();
throw $e;
}
Related
Let's say I have a function that modifies a database. This could be anywhere from one query to multiple queries in a transaction. In any of these modifications we want to make sure all queries are successful. Thankfully for transactions, if one of these fails I have a way of making sure none of the changes are made permanent.
Now let's say I also have a log that needs to be written to to make note of the change (s). How could I make sure that both the modifications are made and the log written?
I could always do a try/catch on either but say for instance my queries are successful but my log is unsuccessful. How could I then go back and undo all of the modifications to the database?
Would something like this be effective and/or advisable?
try {
$db->connect();
$db->mysqli->begin_transaction();
// series of queries
...
// log to file
$db->mysqli->commit();
} catch (exception $e) {
$db->mysqli->rollback();
$db->mysqli->close();
}
Wanted to make note of some behaviors I observed while testing this. If you place the commit() before the log, the database changes will not roll back even if an exception is caught while logging.
The data will be stored in the database once you call commit() or trigger an implicit commit. For example, this will not make any changes to the database because there is no commit:
$mysqli->begin_transaction();
$mysqli->query('INSERT INTO test1(val) VALUES(2)');
// end of script's execution
In your little example, the transaction will be rolled back as long as your logging functionality stops the execution or throws an exception.
$db->connect();
try {
$db->mysqli->begin_transaction();
// series of queries
...
// log to file
throw new \Exception(); // <-- This will prevent commit from executing.
$db->mysqli->commit();
} catch (\Exception $e) {
// An exception? That's ok, we'll try something different instead.
$db->mysqli->rollback();
}
You need to call rollback only if you want to recover from the exception and clear the unsaved buffer. The only time you should ever catch exceptions is if you want to somehow recover from it and do a different thing. Otherwise, your code could be simplified by removing try-catch and rollback altogether.
However, it is a good idea to catch exceptions in transactions and roll it back explicitly as soon as possible, even when you do not want to recover. It can prevent an issue if you catch the exception somewhere else in your code and you try to execute other DB actions. The unsaved data is still in the buffer until you close the session or roll back! This is why you would often see code like this:
$db->connect();
try {
$db->mysqli->begin_transaction();
throw new \Exception(); // <-- Something throws an exception
$db->mysqli->commit();
} catch (\Exception $e) {
// rollback unsaved data, but rethrow the exception as we do not know how to recover from it here
$db->mysqli->rollback();
throw $e;
}
i wonder how does transaction work.
Question is do I need to rollback if any error/exception occurs, where the Exception will be thrown and never reach the commit() call? If so why and how? and what rollback does exactly?
consider the followings PHP code (some Java code does similar things like so):
public function deleteAll():void{
$stmt = $this->con->prepare(
'DELETE FROM dd WHERE 1'
);
$this->con->begin_transaction();
if(!$stmt->execute()){
throw new Exception($stmt->error);
}
$this->con->commit();
}
public function insert(array $list):void{
$stmt = $this->con->prepare(
'INSERT INTO dd(cc) VALUES(?)'
);
$stmt->bind_param('s', $v);
$this->con->begin_transaction();
foreach($list as $v){
if(!$stmt->execute()){
throw new Exception($stmt->error);
}
}
$this->con->commit();
}
Note:
$this->con is the mysqli connection object (not using PDO, although different but PDO and mysqli will do similar thing like above).
database is MySQL, table using InnoDB engine.
Some people code shows they catch the Exception and do rollback. But I did not since the commit() is never executed if Exception is thrown. Then this let me wonder why does it need to rollback at all.
My assumption is if there are multiple queries to perform at once like the following:
public function replaceAllWith(array $list):void{
$this->con->begin_transaction();
try{
//do both previous queries at once
$this->deleteAll();
$this->insert($list);
$this->con->commit();
}catch(Exception $e){
//error
$this->con->rollback();
}
}
but then it still cannot rollback because each method/function is done and committed by different method and different transaction.
Second Question: Do I do each transaction in each method call, or only invoke transaction in the 'boss/parent' function which only call and coordinate 'subordinate/helper' functions to do work?
Not sure if I'm asking the right questions.
Database Transaction is confusing, many online resources show only the procedural process (commit only or then immediately rollback which make no differences to normal direct query) without OO concepts involved. Please any pros guide and give some advice. tq.
I am going to write a long sql transaction and thinking to write reusable sql function so my question is that can i write the transaction in this fashion?
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');
UpdateEmp();
//anothre sql query function, i put him in another function so that i
//can use it seperately
$db->query('second query');
UpdateProject();
$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();
}
whereas other sql functions will be like
function UpdateEmp(){
//separate sql queries for emplyee update
UpdateEmpProject();//nested sql query function
//Do any db error will stop and rollback the trasaction?
}
function UpdateEmpProject(){
//sepeate sql queries for emplyee update
}
My question is that, will the transaction rollback if any error occurred in UpdateEmp function? Suppose UpdateEmp() also contain another function with sql query, then the nested query will also effect the transaction result?
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
I tried to execute some custom queries using DB::execute() function by passing query as a parameter to this function - update query on existing table. Before running this query I checked the connection object like this $connection = DB::getConnection(); and it returned a connection identifier. Then while executing query it returned bool true from execute function, though changes were not there in database table fields. Also if I pass query with wrong syntax, it is giving error.
Is there any rollback process going on in background with update query statement in ActiveCollab? If yes how to stop this rollback to avoid the changes done by my update query ?
Could anyone tell me what could be the issue here ?
All uncommitted transactions are automatically rolled back on script shutdown so you need to make sure that you are committing transactions that you opened:
try {
DB::beginWork();
// Do something
DB::commit();
} catch(Exception $e) {
DB::rollback();
throw $e;
}
or:
DB::transact(function() {
// Do something
});
If you are within a nested transaction and outer transactions gets rolled back, your updates will be rolled back as well.