I have a PHP script that runs a SELECT query then immediately deletes the record. There are multiple machines that are pinging the same php file and fetching data from the same table. Each remote machine is running on a cron job.
My problem is that sometimes it is unable to delete fast enough since some of the machines ping at the exact same time.
My question is, how can I SELECT a record from a database and have it deleted before the next machine grabs it. For right now I just added a short delay but it's not working very well. I tried using a transaction, but I don't think it applies here.
Here is an example snippet of my script:
<?php
$query = "SELECT * FROM `queue` LIMIT 1";
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_array($result)){
$email = $row['email'];
$campaign_id = $row['campaign'];
}
$queryx = "DELETE FROM `queue` WHERE `email` = '".$email."'";
$resultx = mysql_query($queryx) or die(mysql_error());
?>
Really appreciate the help.
If you're using MariaDB 10:
DELETE FROM `queue` LIMIT 1 RETURNING *
Documentation.
well I would use table locks
read more here
Locking is safe and applies to one client session.
A table lock protects only against inappropriate reads or writes by other sessions.
You should use subquery as follows...
<?php
$queryx = "DELETE FROM `queue` WHERE `email` IN (SELECT email FROM `queue` LIMIT 1)";
$resultx = mysql_query($queryx) or die(mysql_error());
?>
*Note: Always select only the fields you want... try to avoid select *... this will slow down the performance
run an update query that will change the key before you do your select. Do the select by this new key, whicj is known only in the same session.
If the table is innoDB the record is locked, and when it will be released, the other selects won't find the record.
Put your delete queries inside the while loop, just incase you ever want to increase the limit from your select.
<?php
$query = mysql_query("SELECT * FROM `queue` LIMIT 1") or die(mysql_error());
while($row = mysql_fetch_array($query)){
mysql_query("DELETE FROM `queue` WHERE `email` = '" . $row['email'] . "' LIMIT 1") or die(mysql_error());
}
?>
The above code would be just the same as running:
mysql_query("DELETE FROM `queue` LIMIT 1") or die(mysql_error());
Be careful using your delete query, if the email field is blank, it will delete all rows that have a blank email. Add LIMIT 1 to your delete query to avoid multiple rows being deleted.
To add a random delay, you could add a sleep to the top of the script,
eg:
<?php
$seconds = mt_rand(1,10);
sleep($seconds);
?>
Related
NOTE: I've edit the whole post, trying to make it clearer.
I'm terrible at getting my question clear, but this is my last try.
I got this which gets sent when clicking the button;
echo"Auto/Prijs<br><br><select name='autos'>";
echo"<br><br>";
$sql = "SELECT `garage`.`id`, `car_id`, `schade`, `naam`, `prijs` FROM `garage` LEFT JOIN `cars` ON (`garage`.`car_id` = `cars`.`id`) WHERE `user_id`=".ID." ORDER BY `id` ASC LIMIT ".($page * 10).", 10";
$sql = mysql_query($sql) or die(mysql_error());
$i = 1;
while($res = mysql_fetch_assoc($sql)){
echo"
<option value='".$res['car_id']."'>".$res['naam']."</option><br>
";
This is a dropdown, showing carnames instead of car_id's.
Now, the car_id is not unique, but refers to a car. The 'id' in the 'garage' table IS unique. Am I able to like call the 'id' too, and on sending check if that ID is actually the sent 'car_id'? Because, you can tamper the sent car_id and simply change it.
This happens on sending:
if(isset($_POST['start'])){
$prijs = $_POST['prijs'];
$carr = $_POST['autos'];
$sql = mysql_query("SELECT `id` FROM `automarkt` WHERE `seller_id`=".ID." LIMIT 1") or die(mysql_error());
mysql_query("INSERT INTO `automarkt`(`seller_id`, `prijs`, `car_id`) VALUES (".ID.", ".$prijs.", ".$carr.")") or die(mysql_error());
I'm out of idea's, and can't get clear enough on what I need to do. I need to check if the sent car_id is actually in the 'user''s garage. (Trying to do it by checking the unique entry 'id' in the 'garage' table.
Fixed it by matching rows.
$sql = mysql_query("SELECT `id` FROM `garage` WHERE `car_id`=".$carr." AND `user_id`=".ID) or die(mysql_error());
} elseif(mysql_num_rows($sql) == 0){
$msgs = bad("x");
Thanks for replies.
i got a point system that are like people can upgrade to [PRO1] user. everyones rights(pro1,pro2,user) are stored in my mysql users table. But i want to make a little feed, that shows the latest one that upgraded to [PRO1]. the upgrade code:
$insert = "UPDATE `users` SET `points` = (`points`-50) WHERE `username` = '".$username."' and points > 50";
mysql_query($insert);
if (mysql_affected_rows() > 0)
{
// other codes
$insert = "UPDATE users SET rights=' [PRO1]' WHERE `username` = '".$username."'";
mysql_query($insert);
header('location: succesupgrade.php');
}else{
echo "You don't have enough points";
}
?>
the upgrade code works fine(just incase i need to add a time/date. and tha code for where i want the"'username' wast the last to upgrade to [PRO1]" is in this code:
<?php
require("dbc.php");
$query = mysql_query("select * from users WHERE rights='[PRO1]' order by right DESC limit 1") or die(mysql_error());
while($array = mysql_fetch_array($query)) {
echo "{$array['username']}<br>";
}
?>was the last to upgrade to:
<?php
require("dbc.php");
$query = mysql_query("select * from users WHERE rights='[PRO1]' order by rights DESC limit 1") or die(mysql_error());
while($array = mysql_fetch_array($query)) {
echo "{$array['rights']}<br>";
}
?>
But that code gives me this error:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DESC limit 1' at line 1
order by right must be order by rights in the first query of the second code block.
That query is going to do nothing to tell you who the last user to upgrade to rights='[PRO1]'. That is just a string field. You would need some sort of datetime/timestamp field that is updated when the users rights change, by which you can make the sort.
You also don't need to do 2 queries. You have two queries doing the exact same thing.
Just do:
SELECT username FROM users WHERE rights='[PRO1]' ORDER BY update_timestamp DESC LIMIT 1
Where update_timestamp would be the field that is updated when the rights change.
The reason is because right is a used keyword, you need a back stroke to solve this :;
Like :
select * from `users` WHERE rights='[PRO1]' order by `rights` DESC limit 1
So I am trying to update my table based on a singe parameter:
The dateEntered field must be blank.
And I want to randomly select 50 rows, and update the blank ownerID fields to "Tester"
Here is what I have:
<?php
include("includes/constants.php");
include("includes/opendb.php");
$query = "SELECT * FROM contacts WHERE dateEntered='' ORDER BY RAND() LIMIT 50";
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_assoc($result)){
$firstid = $row['id'];
$query2 = mysql_query("UPDATE contacts
SET ownerID = 'Tester'
WHERE id = '$firstid'");
$result2 = mysql_query($query2) or die(mysql_error());
}
?>
It will update a single record, then quit and give me:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1' at line 1
The first part that selects the records works fine, its query2 that won't update all 50 records, just one. Maybe I am writing this wrong.
mysql_query needs only one time
$query2 = mysql_query("UPDATE contacts
SET ownerID = 'Tester'
WHERE id = '$firstid'");
$result2 = mysql_query($query2) or die(mysql_error());
to
$result2 = mysql_query("UPDATE contacts
SET ownerID = 'Tester'
WHERE id = '$firstid'");
These answers are spot on, so I will only add some additional information, and a suggestion. When you are querying mysql the first time, $query1 is being set to the result resource, which for
$query1 = mysql_query("UPDATE contacts SET ownerID = 'Tester' WHERE id = '$firstid'");
returns a result of 1 (Boolean TRUE), which is why your second query failed, cause "1" isn't a valid mysql query string. As Greg P stated, you can fix your current script by eliminating the secondary mysql query.
However, you could improve the script entirely, and make fewer sql calls, by using this.
<?php
include("includes/constants.php");
include("includes/opendb.php");
$query = "UPDATE contacts SET owenerID='Tester' WHERE dateEntered='' ORDER BY RAND() LIMIT 50";
$result = mysql_query($query) or die(mysql_error());
I have a table called meta, with two columns name and value.
In a php script, which is called by many clients concurrently, I do this:-
$mysqli->multi_query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan') LIMIT 1000;UPDATE meta SET value=value+1000 WHERE name='scan';");
or this:-
$mysqli->multi_query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan' <b>FOR UPDATE</b>) LIMIT 1000;UPDATE meta SET value=value+1000 WHERE name='scan';");
Unfortunately, this doesn't appear to work as clients are ending up with duplicate id's. The database is heavily loaded and the SELECT takes a few seconds.
$mysqli->autocommit(FALSE);
$mysqli->query("BEGIN;");
$mysqli->multi_query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan' FOR UPDATE) LIMIT 1000;UPDATE meta SET value=value+1000 WHERE name='scan';");
$mysqli->commit();
It's a complex issue; locking and transaction levels, but the magic above was the BEGIN statement. Without it, each statement was running in its own transaction level, and the FOR UPDATE lock was being unlocked too early.
First try below experiment and then try to map as per your requirement.
Create table:
CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`notid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Then create:
create rowlocking.php
<?php
require_once('connectvars.php');
// Connect to the database
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$query = "START TRANSACTION";
$data = mysqli_query($dbc, $query);
$query = "SELECT * FROM t1 WHERE id=5 FOR UPDATE";
$data = mysqli_query($dbc, $query);
if (mysqli_num_rows($data) != 0) {
$row = mysqli_fetch_array($data);
echo $row['id'];
echo $row['notid'];
}
//$query = "COMMIT";
//$data = mysqli_query($dbc, $query);
sleep(10);
echo "After 10 seconds";
?>
Above script will access a row with id=5 and locks for other transaction till 10 second sleeping time.
create rowlocking1.php
<?php
require_once('connectvars.php');
// Connect to the database
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$query = "START TRANSACTION";
$data = mysqli_query($dbc, $query);
$query = "SELECT * FROM t1 WHERE id=5 FOR UPDATE";
$data = mysqli_query($dbc, $query);
if (mysqli_num_rows($data) != 0) {
$row = mysqli_fetch_array($data);
echo $row['id'];
echo $row['notid'];
}
//sleep(10);
//$query = "COMMIT";
//$data = mysqli_query($dbc, $query);
//echo "After 10 seconds";
?>
Above script tries to access same row with id=5.
Now if your run script rowlocking and within that 10 second sleeping time if you run rowlocking1 it will not able to access row id=5 till its release by rowlocking. Once 10 sec sleep time over rowlocking will be able to access row id=5.
Try to map this concept with your script you will get innoDB row level locking. Give a comment if you need detailed explanation.
Is this what you are looking for?
query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan' LOCK IN SHARE MODE) LIMIT 1000 LOCK IN SHARE MODE;
UPDATE meta SET value=value+1000 WHERE name='scan';");
I'm not sure if this is what you are looking for or not, but if so remember to unlock tables afterward when/where necessary and make sure that the account you are using has LOCK privileges.
MySQL Documentation on INNODB READ LOCKS
$sql2 = "SELECT `id` FROM `saa_game` WHERE `domain_id` = '".$row['id']."' AND `unique_id` = '".s($oGame->unique_id)."' AND `year` = '".$iYear."' AND `month` = '".$iMonth."' LIMIT 1";
$result2 = mysql_query($sql2) or die(mail('rpaiva#golevel.com','SAA Gather Error',mysql_error()));
if(mysql_num_rows($result2) == 1)
{
$row = mysql_fetch_assoc($result2);
$sql3 = "UPDATE `saa_game` SET `plays` = '".s($oGame->plays)."' WHERE `id` = '".$row['id']."' LIMIT 1";
$result3 = mysql_query($sql3) or die(mail('email#sample.com','SAA Gather Error',mysql_error()));
}
else
{
$sql3 = "INSERT INTO `saa_game` (`domain_id`,`type`,`source`,`unique_id`,`plays`,`year`,`month`) VALUES ('".$row['id']."','".s($oGame->type)."','".s($oGame->source)."','".s($oGame->unique_id)."','".s($oGame->plays)."','".$iYear."','".$iMonth."')";
$result3 = mysql_query($sql3) or die(mail('email#sample.com','SAA Gather Error',mysql_error()));
}
I've got this set of queries running 40,000 times on a single page load on a cron job every 10 minutes. This takes so long that it almost runs into the next cron job. If I could reduce this into one query instead of two, that'd be great. (as long as there will actually be a performance difference)
If the select hits all of the right indexes, there won't be a performance increase. In fact, it's likely to be worse! MySQL's REPLACE INTO is implemented as a delete, then an insert!
Consider MySQL's INSERT INTO ... ON DUPLICATE KEY UPDATE instead.
Keep in mind that both of these options are exclusive to MySQL.