Rollback multiple update query if one fails - php

So what I am doing is am having multiple UPDATE queries, which changes the group name in the tbl_groups table and then updates all the users in tbl_users which belongs to that group, but if the user update query fails, it updates group, but I want to update both together or none, am using PHP and MySQL.

It sounds like all you need to do is use transactions.
You must use InnoDB tables to use transactions (actually, some other DB engines have transactions, but InnoDB is most common with MySQL).
Issue the "BEGIN TRANSACTION" query before the first update.
If any query fails, issue the "ROLLBACK" query to undo everything.
It's really pretty simple.
And if you ever decide you want to do a partial rollback (back to some point after the beginning of the transaction), then you can use "ROLLBACK TO SAVEPOINT savepoint_name". You will have had to issue the "SAVEPOINT savepoint_name" query 1st.
E.g., in PHP
mysql_query("BEGIN TRANSACTION");
$result1 = mysql_query("UPDATE `tbl_groups` SET `user_id` = 5 WHERE `group_id` = 3");
if($result1 === false) {
mysql_query("ROLLBACK");
}
mysql_query("SAVEPOINT savepoint1");
$result2 = mysql_query("UPDATE `tbl_users` SET `group_id` = 3 WHERE `user_id` = 5");
if($result === false) {
ROLLBACK TO SAVEPOINT savepoint1;
}
// COMMIT saves the changes to the db, making them visible to other sessions
// if the ROLLBACK TO SAVEPOINT statement executed, then only changes up to that SAVEPOINT will be saved
// if no ROLLBACK statements were executed, then all changes will be saved (assuming no MySQL errors that cause implicit ROLLBACK)
mysql_query('COMMIT');

use transactions with the best isolation level
i prefer "repeatable read"( you cannot view others data and vise verse until you committed the transaction ) is always good and it is default for innodb storage engine
if you are not using innodb then change the isolation level with
SET GLOBAL tx_isolation='REPEATABLE-READ'; or
SET SESSION tx_isolation='REPEATABLE-READ';
the rest of details are given by #Buttle Butkus

Set the relation between tbl_groups and tbl_users to ON DELETE CASCADE. Then just update tbl_groups and the changes will cascade to related records in tbl_users. If you have to update your parent keys like this very often then you could maybe think about using a surrogate key instead.

Following example is based on object oriented style, make sure your Required database table Type is InnoDB as it supports transaction statement
Here i just want delete multiple record based on ids
$this->link_id->autocommit(FALSE);
// This above statement start the transaction by disabling auto commit where link_id refers to your MySQLi database connection
for($i=1; $i<=5; $i++){
$tSql = "DELETE FROM your_table"
. " WHERE id=" . $i;
$rs = $this->link_id->query($tSql);// execute the query
if($this->link_id->affected_rows == -1) // check if it fails
{
$_SESSION["error"] = "Sorry ! cant be deleted ! ";
$this->link_id->rollback(); // rollback the transaction
break; // break the loop
}
}
//check if there is some error or not, if no error then commit the transaction
if(empty($_SESSION["error"])){
$this->link_id->commit();
}

Related

Oracle Multiple queries with locked row

I'm using ORACLE version 11g.
I would like to execute three queries "at the same time" and take care that if one or more of theses queries fails must return both tables to the previous state. These queries are one select to know if the selected row still being possible the make the action, and one update and one insert to do the action.
In my case I need to make an update on the same locked row (obviously no one else should be able to do the action to the same row) and later and insert on another table, only if the result of the select query confirm that the selected row still having the option to execute the action, so the queries will be like these approximately:
//this is the row I want to execute the action
$selectedIdFromTable1 = "1";
$query="SELECT attr1 FROM table1 WHERE attr1 = 'oldValueAttr1' AND id = selectedIdFromTable1";
$stmt = $this->oracleDB->prepare($query);
$stmt->bindValue(1, $attr1, "string");
$stmt->execute();
$result = $stmt->fetchColumn();
if($result->num_rows == 1){ //I'm still being able to do the action to the row because the row still having the oldValue
//So here the row must be locked to execute the update and the insert only once. Only one user should execute the update and the insert.
$query="UPDATE table1 SET attr1 = ? WHERE id == $selectedIdFromTable1";
$stmt = $this->oracleDB->prepare($query);
$stmt->bindValue(1, 'newValueAttr1', "string");
$stmt->execute();
$query="INSERT INTO table2 (attr2) VALUES (?)";
$stmt = $this->oracleDB->prepare($query);
$stmt->bindValue(1, 'newValueAttr2', "string");
$stmt->execute();
}
//here the lock can release the row for future actions (but not this one, because if any one tries the previous select should not find anymore the selected row)
Also I'm using the binding system to send the variables more safety. Not sure If can affect the answer.
I'm quite sure that a transaction with locking row is the answer and if it's the answer, I will really appreciate to receive your help with an example of a transaction with Oracle with an example of this situation.
All of that, will be in a Symfony 3.3 project. Probably is not necessary this last information, but the transaction code must be in the symfony project and not in the oracle database for different reasons.
Thank you very much.
If you will use symfony you will most likely use the DBAL connection.
Transactions are handled as described in its documentation
(To me it seems more a transaction feature than a locking one)
Transactions:
$conn->beginTransaction();
try{
// do stuff
$conn->commit();
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
Locking is not handled by DBAL

MySQL ROLLBACK not actually rolling back

I have the following PHP code:
$dbh->beginTransaction();
$dbh->exec("LOCK TABLES
`reservations` WRITE, `settings` WRITE");
$dbh->exec("CREATE TEMPORARY TABLE
temp_reservations
SELECT * FROM reservations");
$dbh->exec("ALTER TABLE
`temp_reservations`
ADD INDEX ( conf_num ) ; ");
// [...Other stuff here with temp_reservations...]
$dbh->exec("DELETE QUICK FROM `reservations`");
$dbh->exec("OPTIMIZE TABLE `reservations`");
$dbh->exec("INSERT INTO `reservations` SELECT * FROM temp_reservations");
var_dump(GlobalContainer::$dbh->inTransaction()); // true
$dbh->exec("UNLOCK TABLES");
$dbh->rollBack();
Transactions are working fine for regular updates/inserts but the above code for some reason is not. When an error happens above, I'm left with a completely empty reservations table. I read on the PDO::beginTransaction page that "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 MySQL manual has a list of "Data Definition Statements", which I would assume is the same as DDL mentioned above which lists CREATE TABLE but I am only creating a temporary table. Is there any way around this?
Also, does the fact that I'm left with an empty reservations table show that a commit occurred after the DELETE QUICK FROM reservations query?
Edit: On an additional note, the INSERT INTO reservations line also produces the following error:
Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
I tried doing $dbh->setAttribute( PDO::MYSQL_ATTR_USE_BUFFERED_QUERY , true); but this doesn't seem to affect it. I'm assuming it would have something to do with the transaction, but I'm not sure. Can anyone pinpoint what exactly is causing this error as well?
Your OPTIMIZE TABLE statement is causing an implicit commit.
I'm not sure exactly what you're trying to do, but it looks you can shorten your code to:
$dbh->exec("OPTIMIZE TABLE `reservations`");
All the other code is just making the job more complex, for no gain.
I'm also assuming you're using InnoDB tables, because MyISAM tables wouldn't support transactions anyway. Every DDL or DML operation on a MyISAM table implicitly commits immediately.
By the way, buffered queries have nothing to do with transactions. They have to do with fetching SELECT result sets one row at a time, versus fetching the whole result set into memory in PHP, then iterating through it. See explanation at: http://php.net/manual/en/mysqlinfo.concepts.buffering.php

PHP MySQL Insert fail after DELETE

I got two tables. One is account, another is Interest.
One account can have multi Interests and It can be edited.
Now, the process is deleting all Interest of this account then insert these insterests.
The QUERY IS:
"DELETE FROM Interests WHERE account_id='$id'"
"INSERT INTO Interests (account_id, interest_name) VALUES('$id', '$name')"
I use the both query when user update their account, but the insert is fail, there is nothing insert into the table (ps. the interests_id is auto_increment and this was be counted) but there is nothing new in the table. When I comment out the delete query. The insert will be successful.
Does any one know what can i do?
If you want to update your table records, you will do update operation.
like this:
UPDATE TABLE_NAME SET FIELD_NAME = 'VARIABLE_NAME'
WHERE PRIMERY_FIELD_NAME = 'VARIABLE_NAME' ;
you did not have to use these two queries, if you want to update data simply use the updat query of mysql.use this:
<?php
$query = "UPDATE Interests SET interest_name = '".$name."' WHERE account_id = '".$id."'" ;
mysql_query($query);
?>
If you want to update your table records then you may execute update operation. It like following
UPDATE Interests
SET
interest_name = '$name'
WHERE
accountno = '$id' ;
Try it. You may solve your problem by this way.
If you have queries failing, you should capture the error and see what went wrong. In all MySQL APIs for PHP, a query that fails returns a status code to indicate this. Examples of checking this status code are easy to find in the docs. But most developers fail to check the status.
Use transactions to ensure that both changes succeed together or neither are applied.
How to Decide to use Database Transactions
Definition of a transaction in MySQL: http://dev.mysql.com/doc/refman/5.5/en/glossary.html#glos_transaction
Syntax for starting and committing transactions in MySQL: http://dev.mysql.com/doc/refman/5.5/en/commit.html
You need to use InnoDB. MyISAM does not support transactions. http://dev.mysql.com/doc/refman/5.5/en/innodb-storage-engine.html
In PHP, you need to stop using the old ext/mysql API and start using MySQLi or PDO.
http://php.net/manual/en/mysqli.quickstart.transactions.php
http://php.net/manual/en/pdo.begintransaction.php
This happens because the query are treated as two single transaction, so the order of execution is not guaranteed.
The effect you are describing is because the insert is processed before delete, so the interests_id is auto-incremented properly, then the row is deleted by delete statement.
You should change the query logic or perform both queries in one single transaction.

Detect a similar request sent to php from same client in a little time difference

I have a php script that receives $_['post'] data from a button in flash , some times the user does double click on that button and the data sends twice , it causes mysql update conflict in server side .
How can i ignore the further requests in php from the same client in a little time diffrence?
Have you ever heard of locking tables in mysql?
It's not possible to provide detailed example without you providing more details, but basic example would be:
mysql_query( "LOCK TABLES updates READ WRITE", $connection);
$q = mysql_query("SELECT COUNT(*) FROM updates WHERE user = $currentUser AND time...");
if( mysql_result($q, 0, 0) != 0){
// Someone's already doing update; exit
mysql_query( "UNLOCK TABLES;");
return false;
}
// Require lock for ourselves
mysql_query( "INSERT INTO updates ...");
mysql_query( "UNLOCK TABLES;");
// Do the stuff on database
This makes sure that when the same user will try to do this twice he or she won't be allowed to (just one update at the time).
You have also different options:
Use TRANSACTION
Generate one time token for updating (beware of atomicity, you'd have to execute DELETE FROM ... and then check affected rows, because SELECT; DELETE my get interrupted or you'd have to use table locking again)
And my favourite one: button.enable=false, and on request completed button.enabled=true
Note: the code is vulnerable against SQL Injection and you mysql_ functions are outdated.
Why not have a last updated timestamp in the database and then check that against the current time. If it's in the last couple of seconds, then don't run the MySQL query.
You can add basic protection mechanism, as you can add a hidden random value to the form and maintain a table of displayed values, from which you delete the used ones before executing the main query. This would also prevent XSS.

mysql: transaction? insert and update

I need to do 2 query.
Basically it's a
mysql_query("INSERT INTO table ($value1,$value2)");
and
mysql_query("UPDATE table2 SET field1 = '$value1', field2 = '$value2'");
I think I can simply do a
if (mysql_query("INSERT ...") !== false) {
mysql_query("UPDATE ...");
}
In this case should I use a transaction? And how should I use it?
Or can i leave that simple if?
Thanks
You will generally use transactions if you want some "all or nothing" behavior.
Basically, with transactions, you can :
Start a transaction
Do the first query
If it succeeds, do the second query
If it succeed, commit the transaction
Else, rollback the transaction -- cancelling both queries that correspond to that transaction.
If working with mysql_* function, you'll have to :
Start the transaction, with a START TRANSACTION query
Do your queries
Depending on the result of those queries, either do a COMMIT or a ROLLBACK query.
To detect whether a query succeeded or not, you can indeed check the return value of mysql_query() : it will return false in case of an error.
Note : MySQL is the old extension -- and doesn't have functions to deal with transactions ; which means you have to deal with them as regular queries.
Working with MySQLi, you could use :
mysqli::autocommit() to disable autocommit
and mysqli::commit() or mysqli::rollback()
For insert and updates MySQL has a good, alternative solution - "ON DUPLICATE KEY UPDATE". It does what you want safely in a single query:
INSERT .... ON DUPLICATE KEY UPDATE
(also the key must be set as unique in this scenario)
http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html

Categories