As far as I understood transaction starts once we call $mysqli->autocommit(FALSE); statement and ends after calling $mysqli->commit(); command like in the example below.
<?php
//Start transaction
$mysqli->autocommit(FALSE);
$mysqli->query('UPDATE `table` SET `col`=2');
$mysqli->query('UPDATE `table1` SET `col1`=3;');
$mysqli->commit();
//End transaction
//Executing other queries without transaction control
$mysqli->query("Select * from table1");
$mysqli->query("Update table1 set col1=2");
//End of executing other queries without transaction control
//Start transaction
$mysqli->autocommit(FALSE);
$mysqli->query('UPDATE `table` SET `col`=2');
$mysqli->query('UPDATE `table1` SET `col1`=3;');
$mysqli->commit();
//End transaction
?>
Have I understood correctly? If not could you please correct me, because it is actually my first time using transactions in real life.
Thank you.
Update Novembre 2020: #Dharman gave a better answer with more details about transactions in mysqli, just check it instead: https://stackoverflow.com/a/63764001/569101 👇
Well according to the php doc, you're right.
<?php
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$mysqli->query("CREATE TABLE Language LIKE CountryLanguage");
/* set autocommit to off */
$mysqli->autocommit(FALSE);
/* Insert some values */
$mysqli->query("INSERT INTO Language VALUES ('DEU', 'Bavarian', 'F', 11.2)");
$mysqli->query("INSERT INTO Language VALUES ('DEU', 'Swabian', 'F', 9.4)");
/* commit transaction */
$mysqli->commit();
/* drop table */
$mysqli->query("DROP TABLE Language");
/* close connection */
$mysqli->close();
?>
In the example above:
the CREATE TABLE is auto committed because it's the default behaviour.
the INSERT INTO aren't auto committed because of the autocommit(FALSE).
the DROP TABLE is auto committed because the autocommit(FALSE) was reset by the ->commit();.
j0k is mainly right, except in the drop table.
The auto commit is not turned on with the ->commit()
Instead, the DROP TABLE is a DDL query, and DDL queries are always implicitly committed and will commit all your previously non committed work.
So, if you did not commit the work, the DDL query would force this commit.
How to use transactions in mysqli?
Prerequisite
In order for the transactions to behave properly you should enable exception error reporting. Otherwise mysqli will not report errors and the transaction will not be performed correctly. Alternatively, you could manually check each query, but that is not recommended. To connect properly with mysqli use the following 3 lines:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'user', 'pass', 'dbname');
$mysqli->set_charset('utf8mb4'); // always set the charset
Transactions only work with transactional tables. Make sure that your table storage engine supports transactions. For example, MyISAM ignores the commit/rollback.
Transactions
There are two possible ways to create a transaction using mysqli. By default all queries/statements are committed as soon as they are performed. You can either switch autocommit off or use a one-time-only transaction.
Transactions are committed to the database in the following situations:
when calling commit
after setting autocommit=1
when starting another transaction
when performing DDL query
and in a few other situations. For more information see Statements That Cause an Implicit Commit
Using autocommit(false)
If you turn autocommit off, you decide when you want to commit, but calling commit() does not switch autocommit back on.
//Start transaction
$mysqli->autocommit(false);
$mysqli->query('INSERT INTO director(name) VALUE("Steven Spielberg")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Jurassic Park';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
$mysqli->commit();
// Changes are committed, but autocommit is not switched back on
// Following queries are still transactional.
// They will not be committed unless you call commit or switch autocommit back on
$mysqli->query('INSERT INTO director(name) VALUE("James Cameron")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Titanic';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
$mysqli->autocommit(true);
// All queries are committed and everything that follows will be immediately committed.
Using begin_transaction()
You can start a one-time-only transaction using begin_transaction(). This does not set autocommit=false so when you call commit() you end the transaction without starting a new one.
//Start transaction
$mysqli->begin_transaction();
$mysqli->query('INSERT INTO director(name) VALUE("Steven Spielberg")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Jurassic Park';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
$mysqli->commit();
// Changes are committed and the transaction has ended
// Following queries will be committed one by one as soon as they are peformed.
$mysqli->query('INSERT INTO director(name) VALUE("James Cameron")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Titanic';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
Performing DDL statements
Some SQL statements trigger an explicit commit but do not affect the value of autocommit.
//Start transaction
$mysqli->autocommit(false);
$mysqli->query('INSERT INTO director(name) VALUE("Steven Spielberg")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Jurassic Park';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
// The following will call commit but it will not set autocommit=true
$mysqli->query('TRUNCATE TABLE movie_genre');
// if you want to switch autocommit back on, you have to call:
$mysqli->autocommit(true);
Rollback
If an exception occurs then PHP will end execution of the script and the code will never reach the commit statement. However, in some situations, you might want to roll back the transaction explicitly, for example to avoid calling commit accidentally somewhere else in the code.
Here is an example of what such a transaction would look like. The second query tries to insert into a non-existent table which means that mysqli will throw an exception. Instead of letting PHP script die, we catch the exception and roll back the transaction. The value 4 will never be inserted into the database because both queries were rolled back.
try {
// Start transaction
$mysqli->begin_transaction();
$mysqli->query('INSERT INTO some_table(col2) VALUE(4)');
$mysqli->query('INSERT INTO does_not_exist(col2) VALUE(4)');
// Commit changes
$mysqli->commit();
} catch (\Throwable $e) {
// Something went wrong. Rollback
$mysqli->rollback();
// Rethrow the exception so that PHP does not continue
// with the execution and the error can be logged in the error_log
throw $e;
}
Prepare SQL statement ONCE, and then execute it SEVERAL times:
<?php
$Mysqli = new mysqli("host","user","pass","base");
// check connection
if(mysqli_connect_errno())
{
printf("Connect failed: %s\n",mysqli_connect_error());
exit();
}
// some data for db insertion
$countries=['Austria','Belgia','Croatia','Denmark','Estonia'];
// explicitly begin DB transaction
$Mysqli->begin_transaction();
// prepare statement (for multiple inserts) only once
$stmt=$Mysqli->prepare("INSERT INTO table(column) VALUES(?)");
// bind (by reference) prepared statement with variable $country
$stmt->bind_param('s',$country);
// load value from array into referenced variable $country
foreach($countries as $country)
{
//execute prep stat more times with new values
//$country is binded (referenced) by statement
//each execute will get new $country value
if(!$stmt->execute())
{
// rollback if prep stat execution fails
$Mysqli->rollback();
// exit or throw an exception
exit();
}
}
// close prepared statement
$stmt->close();
// commit transaction
$Mysqli->commit();
// close connection
$Mysqli->close();
?>
You think that commit automatically switches autocommit back to true?
A comment in the PHP Doc says NO!
Related
Currently I'm testing a execution of a query using laravel 5.4.
I'm trying to print is a query was executed on a transaction, for example:
\DB::enableQueryLog();
try {
\DB::beginTransaction();
$item = new Item();
$item->name = 'some name';
// ...
$item->save();
\DB::commit();
dump(\DB::getQueryLog());
} catch (\Exception $e) {
But query log only show the information of the sql statement, but not tells me complete transaction sql:
I want to print the execution with the transaction.
From mysql table at first want to delete row. Only after the row deleted, i want to select sum(certain_column). Want to be sure that until row deleted, i do not execute select.
Decided to use such code like this:
try{
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$db->beginTransaction();
try{
$stmt_delete = $db->prepare('DELETE FROM `table` WHERE `Id` = 1;');
$stmt_delete_items->execute( );
}
catch (PDOException $e){
echo "DataBase Error: " .htmlspecialchars( $e->getMessage() , ENT_QUOTES, "UTF-8").'<br>';
$db->rollBack();
exit;
}
try{
$stmt_select = $db->prepare('SELECT SUM(`Quantity`) AS `SumQuantity` FROM `table` WHERE `some_column` = 234;');
$stmt_select->execute( );
$arr_select = $stmt_select->fetchAll(PDO::FETCH_ASSOC);
}
catch (PDOException $e){
echo "DataBase Error: " .htmlspecialchars( $e->getMessage() , ENT_QUOTES, "UTF-8").'<br>';
}
$db->commit();
}
Expecting at first execute $stmt_delete and only then $stmt_select. In usual case i get such execution order. But is it guaranteed 100%? Somewhere read that delete may take more time than select... with exit; i prevent next execution only in case of error. But i need to delay $stmt_select until $stmt_delete.
Or may be better to use START TRANSACTION; ... COMMIT;. Or may be for DELETE to use LOCK TABLES ... UNLOCK TABLES.
Please advice solution to be 100% sure that select executes only after delete is finished.
I want to apply transactions but only for this request.is it any impact on insert , update on other table by other users at same.
$conn = ConnectionManager::get('default');
$conn->begin();
try{
//code here
$conn->commit()
}catch(\Exception){
$conn->rollback()
}
unset($conn);
I do a lot of search without success.
I would like to understand the flush() process in this situation.
//Edit updateOptions
public function updateOptions($values){
setOption('country',$values['country']);
setOption('city',$values['city']);
setOption('adress',$values['adress']);
setOption('CP',$values['CP']);
setOption('country_code',$values['country_code']);
}
function setOption($name, $value){
$em = $this->getEntityManager('option.manager');
$option = $this->getOption($name); //Entity Options
$option->setValue($value);
$em->persist($option);
$em->flush();
}
When I look the mysql.log or the profiler, i found this:
START TRANSACTION
UPDATE options SET value = 'France' WHERE name = 'country';
COMMIT
START TRANSACTION
SAVEPOINT DOCTRINE2_SAVEPOINT_2
UPDATE options SET value = 'Paris' WHERE name = 'city';
RELEASE SAVEPOINT DOCTRINE2_SAVEPOINT_2
SAVEPOINT DOCTRINE2_SAVEPOINT_2
UPDATE options SET value = 'Rue de Rivoli' WHERE name = 'adress';
RELEASE SAVEPOINT DOCTRINE2_SAVEPOINT_2
SAVEPOINT DOCTRINE2_SAVEPOINT_2
UPDATE options SET value = '75001' WHERE name = 'CP';
RELEASE SAVEPOINT DOCTRINE2_SAVEPOINT_2
SAVEPOINT DOCTRINE2_SAVEPOINT_2
UPDATE options SET value = '33' WHERE name = 'country_code';
RELEASE SAVEPOINT DOCTRINE2_SAVEPOINT_2
ROLLBACK
Only the first one is updated/committed, I get it, but i don't see why the next are rolled back?
This situation also occur if I use setOption() inside a loop for example.
A help would be great.
Thanks in advance.
Don not call persist() and flush() each time. I assume that could be the reason. Since you don not explicit tell EM to start a transaction it probably tries to guess it by combination of persist() and flush()
try following:
extend your updateOptions method
public function updateOptions ()
{
$em = $this->getEntityManager( 'option.manager' );
$em->beginTransaction();
setOption( 'country', 'France' );
setOption( 'city', 'Paris' );
setOption( 'adress', 'Rue de Rivoli' );
setOption( 'CP', '75001 ' );
setOption( 'country_code', '33' );
$em->flush(); //just notif EM there're records to UPDATE
//$success = false;
try
{
$em->commit();
//$success = true;
}
catch ( \Exception $ex )
{
$em->rollback();
// my fav is followinn: in DEV re-throw exception so you can inspect all in symfony-debug-bar
// in prod just additional emergency log (monolog + swiftmailer) so you get an email
if( $this->get( 'kernel' )->getEnvironment() !== 'prod' )
{
throw $ex;
}
$this->get( 'logger' )->addEmergency( 'Oh nooo! Not again :/' );
}
//return $success;
}
function setOption($name, $value){
$option = $this->getOption($name); //Entity Options
$option->setValue($value);
}
Basically, I have a mysql database where every time I get a row, I want to delete it from the database (after reading its information).
I know I could do something like
$result = mysql_query(" SELECT <some row> FROM table ");
$row = mysql_fetch_array($result);
$id = $row["id"];
mysqli_query(" DELETE FROM table WHERE id=$id");
but now it seems I have two queries going on. Is there a command to tell mysql that I want the row deleted as soon as it gives me the information? I imagine that'd save time and resources.
In my head, it looks like
$result = mysql_query(" SELECT <some row> FROM table THEN DELETE ");
EDIT additional information: I wish to use the SELECTed information after deleting the row. To put it simply, I only want one instance of the information to exist at any give time; it would be as if I were only "moving" a physical copy of the information, so that when it is put on a device/what have you, it is no longer in the table since there is only one copy.
Sorry if my understanding of mysql is rough -- I'm pretty new to it :P
I don't know why you need it but you can use a stored procedure for that:
DELIMITER $$
CREATE PROCEDURE select_and_delete(IN aid INT)
BEGIN
SELECT * FROM table1 WHERE id = aid;
DELETE FROM table1 WHERE id = aid;
END$$
DELIMITER ;
Here is SQLFiddle demo.
mysql_* extension is deprecated, therefore use prepared statements and mysqli or PDO.
Your php code using PDO might look like
$id = 1;
try {
$db = new PDO('mysql:host=localhost;dbname=test;charset=UTF8', 'user', 'userpwd');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$query = $db->prepare("CALL select_and_delete(?)");
$query->execute(array($id));
$result = $query->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "Exception: " .$e->getMessage();
$result = false;
}
$query = null;
$db = null;
//do whatever you need to do with your resultset
var_dump($result);
Following a rather simplified table structure (with the only column id) presented in SQL Fiddle example if you call it with $id=1 you'll you'll get in $result:
array(1) {
[0]=>
array(1) {
["id"]=>
int(1)
}
}
You'll need to add a timestamp field (with default as CURRENT_TIMESTAMP) to be able to tell when the row was added.
Then you can run the following MySQL query.
DELETE FROM `table` WHERE `timestamp` > DATE_SUB(NOW(), INTERVAL 0 SECOND);
You will need to run this as a cron job though. AFAIK it can't be done in MySQL alone.