Locking a MySQL INNODB row in PHP - php

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

Related

How to get columns values after insert using php mysql?

In this code, after insert values to DB.I am doing select query for selecting invoiceNo($sql1= "select invoiceNo from invoices order by invoiceID desc limit 1"; ).Instead of selecting from DB how to get InvoiceNo?
For eg: Assume two users are there.Two users inserts InvoiceID at a same time.While doing "select invoiceNo from invoices order by invoiceID desc limit 1";this will get last coming invoiceID .I need to get specific invoiceID (for particular user) .How to get it?
$query = "select * from invoices order by invoiceID desc limit 1";
$result = $link->query($query);
$row = $result->fetch_assoc();
$invoiceNo = $row["invoiceNo"];
$getinvoiceNo = str_pad($invoiceNo + 1, 4, 0, STR_PAD_LEFT); //inserting like 0000
$sql = "INSERT INTO invoices (invoiceNo)
VALUES ('$getinvoiceNo')";
if ($link->query($sql) === TRUE) {
//echo "1";
$sql1 = "select invoiceNo from invoices order by invoiceID desc limit 1";
$last_id = mysqli_insert_id($link, $sql1);
$result1 = mysqli_query($link, $sql1);
$row1 = mysqli_fetch_array($result1);
echo json_encode($row1);
} else {
echo "ERROR: Could not able to execute $sql. " . mysqli_error($link);
}
mysql_close($link);
If i understand your question correctly, you are concerned about possible data corruption from the concurrent update of the record.
I think you should give a look to mysql SELECT ... FOR UPDATE syntax, it should do what you ask: lock the selected row until an update is fired. Then the lock will be released.
For example:
SELECT table_field FROM table_name WHERE table_id_field = id_param FOR UPDATE
will lock the selected row until
UPDATE table_name SET table_field = table_field + 1 WHERE table_id_field = id_param
If you're looking to prevent collisions in invoice numbers, all you need to do is create your table as
CREATE TABLE invoices (
invoiceID INTEGER NOT NULL AUTO_INCREMENT,
other columns . . .
PRIMARY KEY (invoiceID)
);
Then when you do your INSERT, don't insert the invoiceID and let MySQL do it.
This will ensure that each new invoice has a unique invoiceID.

PHP: How to increment a value in the table row to count views and to limit counts to one IP address

I'm using the following to inset a number into 'mviews' every time some map is viewed.
Question 1. Where in the following code do i add 'ON DUPLICATE KEY UPDATE mviews = mviews+ 1' so it can increment on duplicate?
Question 2. How do i limit counts to one IP?
Question 3. How can i limit this IP to only increment the 'mviews' only within 24 hours; only the first view will be counted on every 24 hours, the rest of the views within 24 hours after the first view are not suppose to be counted.
<?php
require_once 'db_conx.php';
$result = mysql_query( "UPDATE profiles SET mviews = '1' WHERE pid = '2' ") or die (mysql_error());
/*ON DUPLICATE KEY UPDATE mviews = mviews+ 1 */
if($result){
echo "Views + 1";
}
else {
echo "Views inser error";
}
mysql_close($con);
?>
DUPLICATE KEY UPDATE
is used with INSERT and not with UPDATE statement DOCS
count for one IP will be like this
SELECT COUNT(*) FROM profiles WHERE IP = "127.1.0.0";
[If you are not looking for what I have written above]
if IP address is your primary key then
INSERT INTO
profiles (ip,views)
VALUES ("127.1.0.0",1)
ON DUPLICATE KEY
UPDATE views=views+1;
If you want your Code to work properly[dont use mysql_* also escape user input]
<?php
require_once 'db_conx.php';
$result = mysql_query( "SELECT * FROM profiles WHERE pid =2") or die (mysql_error());
/*ON DUPLICATE KEY UPDATE mviews = mviews+ 1 */
if(mysql_num_rows($result) == 0){
mysql_query( "INSERT INTO profiles (views) value(1) ") or die (mysql_error());
}else {
mysql_query( "UPDATE profiles SET mviews = mviews +1 WHERE pid = '2' ") or die (mysql_error());
}
mysql_close($con);
?>
To both Eustatia & Arun Killu - THANK YOU!!! Because of your posted/edited solution, I was helped out of a mind-bending jam. I have to create an addendum (as my currently-resolved situation may be of help to someone reading this).
My situation was how to establish a click-tracking process for a variable-based hyperlink (i.e.);
<a target="_blank" href="<?php echo $link;?>"><b><?php echo $title;?></b></a>
I kept getting the link to echo out the representative information, open the correct, id-tagged link request (and not baffle user expectation of conventional click-behavior), but the ability to MULTIPLY log link clicks was solved by this post. NOTE TO THE SUPRA-FINICKY - YES! PDO IS THE DEFACTO STANDARD.
I apologize PROFUSELY for spreading the rank taint of deprecation, but if I understand what I'm doing now, I should be able to assimilate rudimentary PDO mastery in a 3 - 6 week period.
<?php
/** Connect to DB */
mysql_connect("localhost","database_user","pwd") or die(mysql_error());
mysql_select_db("database_name") or die(mysql_error());
$id = mysql_real_escape_string($_GET['id']);
/** retrieve URL */
$result = mysql_query("SELECT * FROM `articles` WHERE ID = '$id'") or die(mysql_error());
/*ON DUPLICATE KEY UPDATE clicks = clicks+ 1 */
if(mysql_num_rows($result) == 0){
mysql_query("INSERT INTO `articles`(`clicks`) value(1)") or die (mysql_error());
}else {
mysql_query("UPDATE `articles` SET clicks=clicks+1 WHERE `id` = '$id'") or die (mysql_error());
}
$row = mysql_fetch_array($result);
mysql_close();
header("Location: " . $row['link']);
?>
I ended up changing the configuration of the link so the id passed the correct link to the target page;
<?php echo "<a href='pagetracking.php?id=" . $id . $link . "' target='_blank'>
<font color='#0000CC' size='5'><b>" . $row['title']. "</b></font></a>"; ?>
Thank you SO MUCH for posting Eustatia & Arun Killu, I really appreciate the tip. If anyone else here happening across this sees anything that's EXCEPTIONALLY unforgivable (even in this decrepit state, lol) - PLEASE, do - not - hesitate - to scream on it robustly. I need all the help I can get.
As promised for anyone who'd be in need of a WORKING version of this in PDO;
(this is adapted from an answer that I found here on SO, but, can't remember at the second, so I'll look it up for an edit to give the answerer credit.)
track.php
<?php
//Your database information
include '../includes/db.php';
// Fetch id (article_id) from $_GET parameter
$article_id = '';
if (isset($_GET['id'])) $article_id = intval($_GET['id']);
if (!$article_id) {
print "No article ID found!\n";
} else {
//
// Fetch Article ID
//
$sql = "SELECT * FROM `articles` WHERE article_id = :article_id";
$sth = $pdo->prepare($sql);
$sth->bindParam(":article_id", $article_id);
$sth->execute();
}
if (!$sth) {
// No article!
echo 'Invalid Article ID!\n';
} else {
// Article found!
//
// Update Clicks Column
//
$sql = "UPDATE articles SET clicks=clicks+1 WHERE article_id = :article_id";
$sth = $pdo->prepare($sql);
$sth->bindParam(':article_id', $article_id);
$sth->execute();
}
header('Location: view_article.php?id='.intval($_GET['id']));
?>
As this code is working flawlessly, for MY purposes, here's a little caveat for those looking to just blindly cut-and-paste...it updates the clicks column EACH TIME IT'S CLICKED, soooooo if you need to limit it to a certain amount of clicks or some other type of functionality, I wish you well in your endeavors (lol!). I just wanted to publish this as a current, working model of the PDO kind. In fact, I may have to end up using this solution as a basis of another mind-bending jam I'm in right now. In fact, I'm almost willing to bet dollars to donuts I'll be completely right about this move.
HTHSO
P.S. ...PDO is sooooooo annoyingly difficult...but worth it.

Retrieve the latest entry to my Basic Announce Database

Some may know my script Basic Announce, I am trying to get the latest news entry that has been sent through so only the last know entry is called up.
In this code block is the news caller that I originally created but this decides to call all of the entries but thats for the Admin to see, but I need the last entry to be called in this file: news.php
<?php
// Connects to your Database
mysql_connect("$server","$usr","$pswd") or die(mysql_error());
mysql_select_db("$db") or die(mysql_error());
$data = mysql_query("SELECT * FROM announcements")
or die(mysql_error());
while($info = mysql_fetch_array( $data ))
{
Print "<tr>";
Print "<th>Announcement:</th><td>".$info['Announcement'] . "</td> ";
Print "<br>";
Print "<th>Submitted By:</th> <td>".$info['Submitted'] . "</td> ";
}
;
?>
How would I go about select the last know entry I also include my database tables sql code.
Here is the Basic Announce.sql code
CREATE TABLE IF NOT EXISTS `announcements` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`Announcement` text NOT NULL,
`Submitted` text NOT NULL,
`Date_time` date NOT NULL,
PRIMARY KEY (`id`)
);
Is it possible to pull the last record using my Primary Key "id"?
Any help on this troubling matter would be greatly appreciated as I am still learn PHP via self-taught and inspiration.
Many Thanks
Order it by the id descending:
$data = mysql_query("SELECT * FROM announcements ORDER BY id DESC LIMIT 1");
I also added LIMIT 1 because you only want to retrieve the first row in the set. Given you only want a single row, you don't need the while loop, just make a single call to fetch:
$data = mysql_query("SELECT * FROM announcements ORDER BY id DESC LIMIT 1");
if($data && mysql_num_rows($data) == 1) // check for success and a row found
{
$info = mysql_fetch_array( $data );
Print "<tr>";
Print "<th>Announcement:</th><td>".$info['Announcement'] . "</td> ";
Print "<br>";
Print "<th>Submitted By:</th> <td>".$info['Submitted'] . "</td> ";
}
else
{
// no rows or an error occurred
}
Side notes:
The mysql_* library is deprecated. For new code you should consider upgrading to PDO or MySQLi.
.. or die(mysql_error()) should be avoided. A better option is trigger_error(mysql_error()).
try out this.
SELECT Top 1 * FROM announcements order by id desc

SELECT then immediately DELETE mysql record

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);
?>

Simple way to read single record from MySQL

What's the best way with PHP to read a single record from a MySQL database? E.g.:
SELECT id FROM games
I was trying to find an answer in the old questions, but had no luck.
This post is marked obsolete because the content is out of date. It is not currently accepting new interactions.
$id = mysql_result(mysql_query("SELECT id FROM games LIMIT 1"),0);
$link = mysql_connect('localhost','root','yourPassword')
mysql_select_db('database_name', $link);
$sql = 'SELECT id FROM games LIMIT 1';
$result = mysql_query($sql, $link) or die(mysql_error());
$row = mysql_fetch_assoc($result);
print_r($row);
There were few things missing in ChrisAD answer. After connecting to mysql it's crucial to select database and also die() statement allows you to see errors if they occur.
Be carefull it works only if you have 1 record in the database, because otherwise you need to add WHERE id=xx or something similar to get only one row and not more. Also you can access your id like $row['id']
Using PDO you could do something like this:
$db = new PDO('mysql:host=hostname;dbname=dbname', 'username', 'password');
$stmt = $db->query('select id from games where ...');
$id = $stmt->fetchColumn(0);
if ($id !== false) {
echo $id;
}
You obviously should also check whether PDO::query() executes the query OK (either by checking the result or telling PDO to throw exceptions instead)
Assuming you are using an auto-incrementing primary key, which is the normal way to do things, then you can access the key value of the last row you put into the database with:
$userID = mysqli_insert_id($link);
otherwise, you'll have to know more specifics about the row you are trying to find, such as email address. Without knowing your table structure, we can't be more specific.
Either way, to limit your SELECT query, use a WHERE statement like this:
(Generic Example)
$getID = mysqli_fetch_assoc(mysqli_query($link, "SELECT userID FROM users WHERE something = 'unique'"));
$userID = $getID['userID'];
(Specific example)
Or a more specific example:
$getID = mysqli_fetch_assoc(mysqli_query($link, "SELECT userID FROM users WHERE userID = 1"));
$userID = $getID['userID'];
Warning! Your SQL isn't a good idea, because it will select all rows (no WHERE clause assumes "WHERE 1"!) and clog your application if you have a large number of rows. (What's the point of selecting 1,000 rows when 1 will do?) So instead, when selecting only one row, make sure you specify the LIMIT clause:
$sql = "SELECT id FROM games LIMIT 1"; // Select ONLY one, instead of all
$result = $db->query($sql);
$row = $result->fetch_assoc();
echo 'Game ID: '.$row['id'];
This difference requires MySQL to select only the first matching record, so ordering the table is important or you ought to use a WHERE clause. However, it's a whole lot less memory and time to find that one record, than to get every record and output row number one.
One more answer for object oriented style. Found this solution for me:
$id = $dbh->query("SELECT id FROM mytable WHERE mycolumn = 'foo'")->fetch_object()->id;
gives back just one id. Verify that your design ensures you got the right one.
First you connect to your database. Then you build the query string. Then you launch the query and store the result, and finally you fetch what rows you want from the result by using one of the fetch methods.
$link = mysql_connect('localhost','root','yourPassword')
mysql_select_db('database',$link);
$sql = 'SELECT id FROM games'
$result = mysql_query($sql,$link);
$singleRow = mysql_fetch_array($result)
echo $singleRow;
Edit: So sorry, forgot the database connection. Added it now
'Best way' aside some usual ways of retrieving a single record from the database with PHP go like that:
with mysqli
$sql = "SELECT id, name, producer FROM games WHERE user_id = 1";
$result = $db->query($sql);
$row = $result->fetch_row();
with Zend Framework
//Inside the table class
$select = $this->select()->where('user_id = ?', 1);
$row = $this->fetchRow($select);
The easiest way is to use mysql_result.
I copied some of the code below from other answers to save time.
$link = mysql_connect('localhost','root','yourPassword')
mysql_select_db('database',$link);
$sql = 'SELECT id FROM games'
$result = mysql_query($sql,$link);
$num_rows = mysql_num_rows($result);
// i is the row number and will be 0 through $num_rows-1
for ($i = 0; $i < $num_rows; $i++) {
$value = mysql_result($result, i, 'id');
echo 'Row ', i, ': ', $value, "\n";
}
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new mysqli('localhost', 'tmp', 'tmp', 'your_db');
$db->set_charset('utf8mb4');
if($row = $db->query("SELECT id FROM games LIMIT 1")->fetch_row()) { //NULL or array
$id = $row[0];
}
I agree that mysql_result is the easy way to retrieve contents of one cell from a MySQL result set. Tiny code:
$r = mysql_query('SELECT id FROM table') or die(mysql_error());
if (mysql_num_rows($r) > 0) {
echo mysql_result($r); // will output first ID
echo mysql_result($r, 1); // will ouput second ID
}
Easy way to Fetch Single Record from MySQL Database by using PHP List
The SQL Query is SELECT user_name from user_table WHERE user_id = 6
The PHP Code for the above Query is
$sql_select = "";
$sql_select .= "SELECT ";
$sql_select .= " user_name ";
$sql_select .= "FROM user_table ";
$sql_select .= "WHERE user_id = 6" ;
$rs_id = mysql_query($sql_select, $link) or die(mysql_error());
list($userName) = mysql_fetch_row($rs_id);
Note: The List Concept should be applicable for Single Row Fetching not for Multiple Rows
Better if SQL will be optimized with addion of LIMIT 1 in the end:
$query = "select id from games LIMIT 1";
SO ANSWER IS (works on php 5.6.3):
If you want to get first item of first row(even if it is not ID column):
queryExec($query) -> fetch_array()[0];
If you want to get first row(single item from DB)
queryExec($query) -> fetch_assoc();
If you want to some exact column from first row
queryExec($query) -> fetch_assoc()['columnName'];
or need to fix query and use first written way :)

Categories