When I try to run the code below:
$conBud = Propel::getConnection(MyTestBudgetPeer::DATABASE_NAME); // DATABASE_NAME = 'Budget'
$conBud->beginTransaction();
$conIn = Propel::getConnection(MyTestInvoicePeer::DATABASE_NAME); // DATABASE_NAME = 'Invoice'
$conIn->beginTransaction();
$idcl = '1235';
try
{
// Do db udpates related to database Budget (here around 15 tables and 500 data rows are update)
// budExModel is a table, primary id from this table is used to update InvoiceTest Table below
$idtest = $budExModel->save($conBud);
...
// Code to Update one table for database Invoice (only one table)
// Create a Criteria object that will select the correct rows from the database
$selectCriteria = new Criteria();
$selectCriteria->add(InvoiceTestPeer::IDCL, $idcl, Criteria::EQUAL);
$selectCriteria->setDbName(InvoiceTestPeer::DATABASE_NAME);
// Create a Criteria object includes the value you want to set
$updateCriteria = new Criteria();
$updateCriteria->add(InvoiceTestPeer::IDTEST, $idtest);
// Execute the query
BasePeer::doUpdate($selectCriteria, $updateCriteria, $conIn);
$conBud->commit();
$conIn->commit();
} catch (Exception $e)
{
$conBud->rollBack();
$conIn->rollBack();
}
I get error: ["Unable to execute UPDATE statement [UPDATEinvoice_testSETIDTEST=:p1 WHERE invoice_test.IDCL=:p2 ] [wrapped: SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction]
Lock wait timeout exceeded; try restarting transaction
Error I am getting is for the table/db which has lesser data and only processes for one table.
Is this not allowed for mysql?
I already changed innodb_lock_wait_timeout and tried restarting mysql so they are not an option.
Edit: Here IDTEST I am trying to udpate for table invoice_test is an fk from Table Budget_test from database Budget.
It seems that the reason behind the error was foreign key constraint on idtest.
Here $idtest is primary_key of newly saved row from table bud_ex; This retrieved from last_insert_id, this was the same id that is trying to be used in incoice_test table. Problem here is, I was trying to use $idtest, but the connection/transaction wasn't committed hence when trying to use this id, it threw an fk constraint error which in return lock time out exceeded.
To get this to work I had to run a query to set foreign key checks as false for invoice database.
set foreign_key_checks = 0;
Along with this I made certain few changes to the php code to make the try catch block more concrete.
$con1->beginTransaction();
try
{
// Do stuff
$con2->beginTransaction();
try
{
// Do stuff
$con2->commitTransaction();
}
catch (Exception $e)
{
$con2->rollbackTransaction();
throw $e;
}
try
{
$con1->commitTransaction();
}
catch (Exception $e)
{
// Oops $con2 was already committed, we need to manually revert operations done with $con2
throw $e;
}
}
catch (Exception $e)
{
$con1->rollbackTransaction();
throw $e;
}
Related
Here is the database and PHP information:
Database vendor and version : 10.2.32-MariaDB
PHP Version : PHP 7.3
I am running into an issue when trying to retrieve the last inserted id to use in another insert statement using PHP PDO and MariaDB...
Sorry for the vague pseudo-code below but trying to mask proprietary data:
try {
include_once $pdo connection stuff here;
$pdo->beginTransaction();
$sql = 'AN INSERT STATEMENT HERE';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':some_value', $some_value);
$stmt->bindValue(':another_one', $another_one);
$stmt->bindValue(':additional_value', $additional_value);
$stmt->execute();
// have tried to call $pdo->commit(): here to no avail.
//should get the last inserted id here on the AUTO_INCREMENT column in the target table from above prepared statement
// the AI column is not included in the insert statement above nor any value specified in the VALUES clause so should
// set to the next available value (and does so according to peeking at row over in phpMyAdmin).
$last_insert_id = $pdo->lastInsertId();
// don't really want to commit the above insert here just yet in case something goes wrong below and can rollback
// a file could be uploaded but it's not mandatory
if (!empty($_FILES['some_file'])) { // file has been attached.
// some file operations here
// some file operations here
// some file operations here
// some file operations here
$extensions = array("extension I am expecting");
if (in_array($file_ext, $extensions) === false) {
//Uh-oh not the correct extension so rolling back
$pdo->rollback();
die('message here...');
} else {
// file type is ok so proceeding
// if the file already exists, get rid of it so we don't have 2 copies on the server
if (file_exists($file_dir.$file_name)) {
unlink($file_dir.$file_name);
}
// storing the attached file in designated directory
move_uploaded_file($file_tmp, $file_dir.$file_name);
// going to parse the file...
$xml = simplexml_load_file('xml file to parse');
// have tried to call $pdo->commit(): here to no avail.
foreach ($xml->children() as $row) {
foreach ($row as $obj) {
if (some checking things with the obj here yada yada yada) {
$insert_sql = "INSERT INTO another table(columns.....) //there is no AUTO_INCREMENT column attribute on any column in this table just FYI
VALUES(column values...)";
$stmt = $pdo->prepare($insert_sql);
// want the AI value here from the very first insert above but it's always zero (0)
$stmt->bindValue(':last_insert_id', intval($last_insert_id), PDO::PARAM_INT);
$stmt->bindValue(':some_column', strval($some_column));
$stmt->bindValue(':another_one', strval($another_one));
$stmt->execute();
}
}
}
// all is good so committing the first insert
$pdo->commit();
}
} else {
// the file was not uploaded and it is not mandatory so committing the first insert here and the second insert never happens
$pdo->commit();
}
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollback();
}
throw $e;
echo 'An error occurred.';
echo 'Database Error '. $e->getMessage(). ' in '. $e->getFile().
': '. $e->getLine();
}
}
My goal is that the first insert always gets inserted (should nothing fail in it). The second insert is optional depending if a file is attached.
If the file is attached and all the file operations are good, then I'll insert some values in another table and use the auto_increment value from the first insert in this second table ( the idea is as a foreign key).
But for whatever reason, the value inserted is always zero (0).
When the code executes successfully both table inserts complete (granted a file is present and the second insert even fires)...
The row in the first table is created and 1 or more rows in the second insert's table are created but they have a value of 0 in the designated column, where I would expect them to contain the AI value from the first insert...
I've tried to call $pdo->commit() in several other places that "make sense" to me thinking that the first insert must be committed for an AI value to even exist on that table but no luck with any of them...
I even tried this I saw in another Stackoverflow post as a test to make sure PDO isn't doing anything wonky, but PDO is fine...
$conn = new PDO(connection info here);
$conn->exec('CREATE TABLE testIncrement ' .
'(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50))');
$sth = $conn->prepare('INSERT INTO testIncrement (name) VALUES (:name)');
$sth->execute([':name' => 'foo']);
var_dump($conn->lastInsertId());
And the above does return: string(1) "1"
So I think PDO is ok (granted the above was not wrapped in a transaction and I haven't tried that yet)
Hope I have provided enough clear details...
Does anyone know why I am getting 0 and not the last insert id?
Any help is greatly appreciated and thank you!
You need to check the result of $stmt->execute. Read the docs on PDOStatement::execute and you'll see that it returns a boolean value:
Returns TRUE on success or FALSE on failure.
Then read the docs on PDOStatement::errorInfo. Check this if execute returns FALSE.
$stmt->execute();
echo "\nPDOStatement::errorInfo():\n";
$arr = $stmt->errorInfo();
print_r($arr);
EDIT: it's not generally a good idea to output errors to the screen, I did so in this case for convenience. A better approach would be to write a log file:
$arr = $stmt->errorInfo();
file_put_contents("/path/to/file.log", print_r($arr, TRUE));
I wonder whether it matters where to start the transaction.
Example 1:
$transaction = Yii::app()->db->beginTransaction();
try
{
$savedSuccessfully = $object->save();
$transaction->commit();
}
catch (Exception $ex)
{
$transaction->rollBack();
$result = $e->getMessage();
}
Example 2:
$transaction = Yii::app()->db->beginTransaction();
try
{
$object = $model()::model()->findByPk(1); //!!!!!!! - line
// what makes the difference
$savedSuccessfully = $object->save();
$transaction->commit();
}
catch (Exception $ex)
{
$transaction->rollBack();
$result = $e->getMessage();
}
Should transaction be started before selecting data from db or or just before updating/inserting data? Will yii take care of it instead of me?
Thanks
Example 2 would be the solution of choice.
By retrieving the model within the transaction, you make sure that it is consistent throughout your changes.
If you retrieve the model, like in example 1, outside the transaction, other threads/users could change the corresponding database entry before you commit your changes. So you could end up with potentially inconsistent data.
Actually 2nd one is correct , if you are saving data which is more critical like banking transaction or payment system then example 2 is very correct way. for example , you are doing some code like this.
insert into table 1
select from table 1
insert into table 2
update table 2
select from table 1.
so if you start transaction from first , it will rollback all query if any query fails which will be more efficient. for example in online payment system.
I'm using PHP with PDO and InnoDB tables.
I only want the code to allow one user-submitted operation to complete, the user can either cancel or complete. But in the case that the user posts both operations, I want one of the requests to fail and rollback, which isn't happening right now, both are completing without exception/error. I thought deleting the row after checking it exists would be enough.
$pdo = new PDO();
try {
$pdo->beginTransaction();
$rowCheck = $pdo->query("SELECT * FROM table WHERE id=99")->rowCount();
if ($rowCheck == 0)
throw new RuntimeException("Row isn't there");
$pdo->exec("DELETE FROM table WHERE id = 99");
// either cancel, which does one bunch of queries. if (isset($_POST['cancel'])) ...
// or complete, which does another bunch of queries. if (isset($_POST['complete'])) ...
// do a bunch of queries on other tables here...
$pdo->commit();
} catch (Exception $e) {
$pdo->rollback();
throw $e;
}
How can I make the cancel / complete operations a critical section? The second operation MUST fail.
Another solution just for completeness:
private function getLock() {
$lock = $this->pdo->query("SELECT GET_LOCK('my_lock_name', 5)")->fetchColumn();
if ($lock != "1")
throw new RuntimeException("Lock was not gained: " . $lock);
}
private function releaseLock() {
$releaseLock = $this->pdo->query("SELECT RELEASE_LOCK('my_lock_name')")->fetchColumn();
if ($releaseLock != "1")
throw new RuntimeException("Lock not properly released " . $releaseLock);
}
MySQL GET_LOCK() documentation
The code is fine, with one exception: Add FOR UPDATE to the initial SELECT. That should suffice to block the second button press until the first DELETE has happened, thereby leading to the second one "failing".
https://dev.mysql.com/doc/refman/5.5/en/innodb-locking-reads.html
Note Locking of rows for update using SELECT FOR UPDATE only applies
when autocommit is disabled (either by beginning transaction with
START TRANSACTION or by setting autocommit to 0. If autocommit is
enabled, the rows matching the specification are not locked.
I started coding a simple script that reads IDs from CSV and then sets the same email address (in DB) for ALL users from CSV. Simple enough...(using PDO)
reading of data from CSV is ok. It's the update part that is giving me headaches.
$sSQL = "UPDATE users SET email = 'something#something.com' WHERE CUSTOMER_ID = '%s'";
foreach ($aUsers as $sCustomerId) {
$sQuery = sprintf($sSQL, $sCustomerId);
if (!$db->exec($sQuery)) {
printf("There was an error updating user %s in database.<br>", $sCustomerId);
$aFailed[] = $sCustomerId;
} else {
printf("User %s successfully updated.<br>", $sCustomerId);
$success++;
}
}
script is really easy. The problem is that mysql doesn't UPDATE the user in DB if he already has that email address so it will return 0 (0 rows affected which is ok). Which is also the same thing mysql returns if error occures during update. And that is what's driving me nuts. How do I distinguish between "no rows affected" and "error occured". I tried using different PDO::ATTR_ERRMODEs and was expecting that at least in the case of error it will throw an exception (which kind of PDO::ERRMODE_EXCEPTION should do) which I can than catch but it doesn't. Then I trid to use
if ($db->exec($sQuery) === FALSE)
also doesn't work. The script now works but the problem is that I can't distinguish "error" from "no affected rows".
Is that behavior in mysql new or? Because I can't remember having that problem in the past when writing such simple scripts for update...
Manual says: "If you set a column to the value it currently has, MySQL notices this and does not update it."
You probably could use errorCode() AND errorInfo() to identify an error.
errorCode() function returns NULL, if query has no problems.
Example:
$db->exec($sQuery);
if ($db->errorCode() !== NULL) {
// Additional error info from: $db->errorInfo()
printf("There was an error updating user %s in database.<br>", $sCustomerId);
$aFailed[] = $sCustomerId;
} else {
printf("User %s successfully updated.<br>", $sCustomerId);
$success++;
}
PHP documentation:
http://php.net/manual/en/pdo.errorcode.php
http://php.net/manual/en/pdo.errorinfo.php
If you are using PDO, try to do prepare and then execute, which returns true on success, false on error
$sSQL = "UPDATE users SET email = 'something#something.com' WHERE CUSTOMER_ID = :customer_id";
$sth = $db->prepare($sSQL);
if(!$sth->execute(array(':customer_id' => $sCustomerId)))
{
printf("There was an error updating user %s in database.<br>", $sCustomerId);
} else {
printf("User %s successfully updated.<br>", $sCustomerId);
}
It's a good pratice for keeping your script secure. Execute will bind params, so there is no need to escape or quote them.
For relational databases like mysql, transaction handling in PHP is just like.
Begin transaction
...
Insert queries
...
Update queries
...
if error in any query then
Rollback transaction
...
at end, if no error in any query then
Commit transaction
How to handle transactions in neo4jphp?
I have tried same like but there was failure. Even after rollback changes were saved.
I was doing like this.
//$client = Neo4jClient
$transaction = $client->beginTransaction();
...
//Insert queries
...
//Update queries
...
//if error in any query then
$transaction->rollback();
...
// at end, if no error in any query then
$transaction->commit();
Check following code.
//$client = Neo4jClient
$transaction = $client->beginTransaction();
$dataCypherQuery = new Query($client, $dataQuery, $params);
Instead of getting resultset from query, we need to add statement into transaction.
// $dataResult = $dataCypherQuery->getResultSet(); // don't do this for transaction
Important : Pass query object to transaction's add statements method.
$dataResult = $transaction->addStatements($dataCypherQuery);
We can pass true as parameter indicating transaction commit.
//$dataResult = $transaction->addStatements($dataCypherQuery, true);
If there is an error, changes are automatically rolled back.
You can check $dataResult variable for validity, result should be returning something.
if (0 == $dataResult->count()) {
$transaction->rollback();
}
At end, if no error in any query then
$transaction->commit();
For more info see Cypher-Transactions