I have an script of a queue like situation which each user can post a request which is added to the queue and others can accept these requests by the order of that queue but each request should only be accepted by one user .
but if I implement it with two queries one for the select other for the delete
something Like this
<?php
$sql = "SELECT id,CreationDate FROM `RandomQueue` order by CreationDate limit 1 ;";
$result = $conn->query($sql);
if ($result->num_rows == 1) {
$row = $result->fetch_assoc();
echo "this request is accepted".$row['id'];
$sqlDel = "DELETE FROM `RandomQueue` WHERE id = {$row['id']}";
$conn->query($sql);
}
?>
it would not work because many problems may occur like two user accepting one request and so on..(mutex problem) is it possible to achieve this by one sql query(to make it atomic)? and if not then any suggestion on how to implementing this logic would be nice ,thanks a lot;
Atomicity is one of the database transaction properties (ACID), so you need make sure your database engine support transaction.
In MySQL, MyISAM doesn't support transaction and you must use InnoDB.
If you are using InnoDB, there are two ways to start database transaction for MySQL in PHP
/* Begin a transaction, turning off autocommit */
$dbh->beginTransaction();
// your business logic goes here
$dbh->commit();
or
$mysqli->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
// your business logic goes here
$mysqli->commit();
Related
i have two table in sql the 1st table is for account while the 2nd table is for testimonial . i am trying to update the two tables in single query. The update is successful if the account already have a testimonial but fails to update if the account has no testimonial yet .How can i fix this heres my code for the update ....
if(!$update=mysql_query(
"UPDATE
tblapplicant,
tbltestimonial
SET
tblapplicant.ImagePath='".$name."',
tbltestimonial.pic = '".$name."'
WHERE
tblapplicant.appid=tbltestimonial.appid"
)
)
1) You're working with a database, it defeats the purpose to use the same data being inserted into two different tables.
2) One gentleman also mentioned stop using MySQL... heres some reference code for you. Assuming you're using php.
3) If you want to use a single query to update 2 tables with the same info against recommendation. Use a stored procedure to update them both.
4) At which point are these account's interconnected in this query? I'm somehow intrigued if this system is in beta or testing?
With your "Where" conditions without matching a specific record, this will update every record that has a matching ID. This is highly not recommended until you add further conditions like username = .... or a condition that's specific to someone or a specific set of rows.
**I strongly advise you post the tables you're working with and what results you want achieve for the best advise. **
Can't really give a good consultation with you playing the whole overview close to the chest. Using this plain-Jane without further detail on what you're asking for is at your own risk my friend.
include/dbconnect.php optional recommended update
<?php
if (isset($mysqli)){
unset($mysqli);
}
define("HOST", "yo.ur.ip.addr"); // The host you want to connect to.
define("USER", "myfunctionalaccount"); // The database username.
define("PASSWORD", "superdoopersecurepassword!"); // The database password.
define("DATABASE", "thegoods");
$mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE);
if ( $mysqli->connect_error ) {
die('Connect Error: ' . $mysqli->connect_error);
}
?>
functions.php <-- shouldn't be called functions if its going to be your form response
<?php
// SHOULD BE SOME MASSIVE LOGIC UP HERE FOR FORM DATA DECISIONING
include_once "include/dbconnect.php";
$name = addslashes($_FILES['image']['name']);
$image = mysql_real_escape_string(addslashes(file_get_contents($_FILES['image']['tmp_name'])));
if ($stmt = $mysqli->prepare("CALL UpdateTestimonials(?,?,?)"){
$stmt->bind_param($name, $image, $userid);
$stmt->execute();
// optional to show affected rows
$stmt->affected_rows
//
// use if you want to return values from DB
// $stmt->bind_result($result);
// $stmt->fetch;
}
$stmt->close
?>
MySQL build a stored procedure - fyi; definer is optional. Definer will allow you to run a query that only elevated privileges can access due to the safety of such a query. You can use create procedure w/o the definer parameter. dT is just an abbreviation for datatype. You would put varchar or int... etc..
use 'database';
DROP procedure if exists 'UpdateTestimonials';
DELIMITER $$
use 'UpdateTestimonials' $$
CREATE DEFINER='user'#'HOSTNAME/LOCALHOST/%' PROCEDURE 'mynewprocedure' (IN varINPUT varchar, IN varIMG blob, IN varAppID int)
BEGIN
UPDATE tblapplicant
SET imagepath = varINPUT,
pic = LOAD_FILE(varIMG)
WHERE appid = varAppID
END $$
DELIMITER;
Use LEFT JOIN:
if (!$update = mysql_query(
"UPDATE
tblapplicant
LEFT JOIN tbltestimonial ON tblapplicant.appid = tbltestimonial.appid
SET
tblapplicant.ImagePath = '" . $name . "',
tbltestimonial.pic = '" . $name . "'"
)
)
Also, if you need some additional filters for tbltestimonial, add them into LEFT JOIN condition
You could try with a transaction. BTW also please use prepared statements to prevent SQL Injection attacks.
<?php
// prefer mysqli over mysql. It is the more modern library.
$db = new mysqli("example.com", "user", "password", "database");
$db->autocommit(false); // begin a new transaction
// prepare statements
$update_applicant =
$db->prepare("UPDATE tblapplicant
SET tblapplicant.ImagePath = ?"));
$update_applicant->bind_param("s", $name));
$update_applicant->execute();
$update_testimonial =
$db->prepare("UPDATE tbltestimonial
SET tbltestimonial.pic = ?"));
$update_testimonial->bind_param("s", $name))
$update_testimonial->execute();
$db->commit(); // finish the whole transaction as successful,
// when everything has succeeded.
?>
Of course that would not create any testimonials, that don't exist. It just updates those, that do. When you want to insert new entries in tbltestimonial, do so explicitly with an INSERT statement inside the transaction.
MySQL does not fully support transactions. The tables have to use a table type, that can handle them, e.g. innodb. When that is the case, the transaction will make sure, that everyone else either sees all changes from the transactions, or none.
In many cases transactions allow you to perform a group of simple steps, that otherwise would need complex single queries or would not be possible without transactions at all.
Alternative Solution
Another approach of course would be an update-trigger. Create a trigger in your database, that fires whenever e.g. tblapplicant is updated and updates tbltestimonial accordingly. Then you don't have to care about that in your application code.
I thought you guys would know the best way to do this:
When I delete an order ($prodId) from the ORDERS table, this script then goes and deletes all the items-ordered lines from the ORDERED_ITEMS table, which houses all the items ordered from every order in the system.
Is there a best practice to ensure that what I want deleted is deleted and only that? I'm worried about something going wrong/injected/mistyped with/into the script and accidentally deleting all the ordered item lines for all orders by mistake.
This is how far I got.
$delete_prod_items = mysqli_real_escape_string($con,$_REQUEST['prodId']);
if (is_numeric($delete_prod_items)){
$sql3 = "DELETE from proteus.ordered_items where order_id = $orderId";
mysqli_query($con,$sql3) or die('DELETE Order $orderId from the Ordered Items table failed: ' . mysqli_error($con).'<br>');
}
This script is POSTed into by my form.
$orderID is the order number that the script uses to identify which ITEM rows should be deleted
$delete_prod_item is the escaped $prodID value. I was trying to be super cautious. perhaps I don't need this.
Am I missing anything?
Don't know why anyone is mentioning it, but the best way to really protect any statement is using PreparedStatements:
$delete_prod_items = mysqli_real_escape_string($con,$_REQUEST['prodId']);
$mysqli = new mysqli("localhost", "localuser", "password", "database");
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: " . $mysqli->connect_error;
}
if (!($stmt = $mysqli->prepare("DELETE FROM `proteus`.`ordered_items` WHERE `order_id` = ?"))) { //whatever query you want
echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
$stmt->bind_param("s", $delete_prod_items);
$stmt->execute();
$stmt->close();
Also take after what #zerkms mentioned and use POST requests for your information.
First things first you need to take care of the sql injections. The link will give you an idea.
Secondly you could use javascript to get a pop-up which asks the user a confirmation before deletion.
Next, to avoid the unintentional deletion of more than one row is to include LIMIT 1 to your query.
N.B. You could also limit priveleges by creating a different user (username) to access the mysql database use it in your mysql_connect('host', 'username', 'password', 'database') function . If you are displaying something really important you may consider not giving deletion rights.
Escaping data is good for preventing SQL Injection
It's better to limit the request with $_POST instead of $_REQUEST
If this page is for admin only, then it's fine since only you have access, however, if users have access to it, therefore it's better to apply some condition to the request before proceeding to the deletion (example, verify that they are deleting something related to their accounts only)
As a first order of business, do the following...
Limit the request method to POST or even better, DELETE if you can get PHP to handle it.
Use prepared statements with bound parameters to avoid SQL injection vulnerabilities.
The main issue you will face is unauthorised requests. The best solution to this is to use a CSRF token.
I have the following query which does not work when it is initiated by my PHP code:
$sql = 'START TRANSACTION;
DELETE FROM task_actions
WHERE task_id='.$id.';
DELETE FROM tasks
WHERE id='.$id.';
COMMIT;
';
When I echo $sql and put the output directly into phpMyAdmin, it works without a problem; and when I had it done in two steps instead of one transaction, it worked from my PHP code, too.
I first thought MySQL might not allow transactions, but stackoverflow.com/questions/2050310 and stackoverflow.com/questions/2960012 showed that was wrong.
I found I could disable autocommit, do both queries and reactivate autocommit (stackoverflow.com/a/17607619 & stackoverflow.com/a/12092151), but I would prefer not to.
Any ideas why it does not work?
$sql = 'START TRANSACTION';
// run this query
$sql = 'DELETE FROM task_actions WHERE task_id=?';
// run this query
$sql = 'DELETE FROM tasks WHERE id=?';
// run this query
$sql = 'COMMIT';
// finally run this one
I'm having trouble getting a table to lock in MySQL. I've tried testing for concurrent requests by running two scripts at the same time on different browsers.
Script 1:
mysql_query("lock tables id_numbers write");
$sql = "select number from id_numbers";
$result = mysql_query($sql);
if ($record = mysql_fetch_array($result))
{
$id = $record['number'];
}
sleep(30);
$id++;
$sql = "update id_numbers set number = '$id'";
$result = mysql_query($sql);
mysql_query("unlock tables");
Script 2:
$sql = "select number from id_numbers";
$result = mysql_query($sql);
if ($record = mysql_fetch_array($result))
{
$id = $record['number'];
}
echo $id;
I start Script 1 first, then start Script 2. Theoretically, Script 2 should wait 30+ seconds until Script 1 unlocks the table, and then output the updated ID. But it immediately outputs the original ID, so the lock is obviously not taking effect. What could be going wrong?
BTW, I know I shouldn't still be using mysql_*, but I'm stuck with it for now.
EDIT: I've discovered that the lock does happen on the live site, but not on my dev site. They are both on shared hosts, so I'm assuming there's some setting that's different between the two. Any idea what that could be?
Using the code you have here, I get exactly the behavior you expect: script 2 will block until script 1 issues the unlock tables.
Your problem must lie elsewhere. Things to check:
Does the table actually exist on the database? (Check your mysql_query() return values and use mysql_error() if you get any FALSE returns.)
Are both scripts connecting to the same database?
Are you sure the table is not a temporary (thus connection-local) table?
Could script 1's mysql connection (or the script itself) be timing out during the sleep(30), thus releasing the lock earlier than you expect?
I've two DB servers db1 and db2.
db1 has a table called tbl_album
db2 has a table called tbl_user_album
CREATE TABLE tbl_album
(
id PRIMARY KEY,
name varchar(128)
...
);
CREATE TABLE tbl_user_album
(
id PRIMARY KEY,
album_id bigint
...
);
Now if a user wants to create an album what my php code needs to do is:
Create a record in db1 and save its id(primary key)
Create a record in db2 using it saved in first statement
Is it possible to keep these two statements in a transaction? I'm ok with a php solution too. I mean I'm fine if there is a solution that needs php code to retain db handles and commit or rollback on those handles.
Any help is much appreciated.
Yes it is possible, but do you really need it?
Think twice before you decide this really must be two separate databases.
You could just keep both connections open and ROLLBACK the first command if the second one fails.
If you'd really need prepared transactions, continue reading.
Regarding your schema - I would use sequence generators and RETURNING clause on database side, just for convenience.
CREATE TABLE tbl_album (
id serial PRIMARY KEY,
name varchar(128) UNIQUE,
...
);
CREATE TABLE tbl_user_album (
id serial PRIMARY KEY,
album_id bigint NOT NULL,
...
);
Now you will need some external glue - distributed transaction coordinator (?) - to make this work properly.
The trick is to use PREPARE TRANSACTION instead of COMMIT. Then after both transactions succeed, use COMMIT PREPARED.
PHP proof-of-concept is below.
WARNING! this code is missing the critical part - that is error control. Any error in $db2 should be caught and ROLLBACK PREPARED should be executed on $db1
If you don't catch errors you will leave $db1 with frozen transactions which is really, really bad.
<?php
$db1 = pg_connect( "dbname=db1" );
$db2 = pg_connect( "dbname=db2" );
$transid = uniqid();
pg_query( $db1, 'BEGIN' );
$result = pg_query( $db1, "INSERT INTO tbl_album(name) VALUES('Absolutely Free') RETURNING id" );
$row = pg_fetch_row($result);
$albumid = $row[0];
pg_query( $db1, "PREPARE TRANSACTION '$transid'" );
if ( pg_query( $db2, "INSERT INTO tbl_user_album(album_id) VALUES($albumid)" ) ) {
pg_query( $db1, "COMMIT PREPARED '$transid'" );
}
else {
pg_query( $db1, "ROLLBACK PREPARED '$transid'" );
}
?>
And again - think before you will use it. What Erwin proposes might be more sensible.
Oh and just one more note... To use this PostgreSQL feature, you need to set max_prepared_transactions config variable to nonzero value.
If you can access db2 from within db1, then you could optimize the process and actually keep it all inside a transaction. Use dblink or SQL MED for that.
If you roll back a transaction on the local server, what has been done via dblink on a remote server will not be rolled back. (That is one way to make changes persistent even if a transaction is rolled back.)
But you can execute code on the remote server that rolls back if not successful, and only execute it, if the operation in the local db has been successful first. If the remote operation fails you can roll back locally, too.
Also, use the RETURNING clause of INSERT to return id from a serial column.
It will be easier with PDO...
The main advantage of PDO is to capture errors (by PHP error line or returning SQL error messages) of each single SQL statment in the transaction.
See pdo.begintransaction, pdo.commit, pdo.rollback and pdo.error-handling.
Example:
$dbh->beginTransaction();
/* Do SQL */
$sth1 = $dbh->exec("CREATE TABLE tbl_album (..)");
$sth2 = $dbh->exec("CREATE TABLE tbl_user_album(..)");
/* Commit the changes */
$dbh->commit();