How to lock mysql tables in php - php

How do I lock mysql tables in php? I currently have this code:
$db->query("LOCK TABLES tbl_othercharge WRITE");
for($x=0;$x<=500; $x++){
$id = get_max();
$db->query("INSERT INTO tbl_othercharge SET tblocID = '$id', assessmentID='lock1'");
}
$db->query("UNLOCK TABLES");
Here's the get_max() function, which obviously will fail if the script above executes simultaneously.
<?php
function get_max(){
global $db;
$max = $db->get_var("SELECT MAX(tblocNumber) FROM tbl_othercharge");
if($max == null){
$max = 1;
}else if($max >= 1){
$max = $max + 1;
}
return 'OC'.$max;
}
?>
I'm trying to test if there are still concurrency problems by executing the same script on 2 browsers.
The script above inserts 400+ records instead of 999 records. How do I properly lock the table while I'm inserting something into it.
I want to lock the table to prevent something like this to happen:
As you can see the field with the prefix 'OC' on it should have a number which is equal to the auto-increment primary key.

The only reliable solution is to do an insert with a dummy value, getting the last insert id, and updating the row to the correct value.
mysql_query("INSERT INTO table (field) VALUES (dummy);");
$id = mysql_last_insert_id();
mysql_query("UPDATE table SET field='OC{$id}' WHERE id={$id} LIMIT 1;");

I'd suggest to drop the 'OC' field from the table, e.g.
CREATE TABLE tbl_othercharge (tblocID int AUTO_INCREMENT PRIMARY KEY, assessmentID varchar(100));
CREATE VIEW vw_othercharge SELECT tblocID, concat('OC',tblocID) as OCnumber, assessmentID FROM tbl_othercharge
now direct all relevant SELECTs to vw_othercharge and forget about it.

Have you try:
for($x=0;$x<=500; $x++){
$db->query("LOCK TABLES tbl_othercharge WRITE");
$id = get_max();
$db->query("INSERT INTO tbl_othercharge SET tblocID = '$id', assessmentID='lock1'");
$db->query("UNLOCK TABLES");
}
In this way you will set lock each time you insert a row!

Related

Select the last affected rows

I have updated my records based on specific condition after that I want to know the ids from the affected rows.
$sql = mysqli_query("update table set xxx='".$x."' where yyy='".$y."'");
Now after executing this query I want to know the affected rows.
Simple yet effective
$last_id = mysqli_insert_id($conn);
http://www.w3schools.com/php/php_mysql_insert_lastid.asp
You must first fetch the IDs, and then perform the update. If concurrency is a concern, you can use a locking read (provided that your table is stored in a transactional engine, such as InnoDB):
$mysqli->autocommit(FALSE);
$select = $mysqli->prepare('SELECT id FROM table WHERE yyy = ? FOR UPDATE');
$select->bind_param('s', $y);
$select->execute();
$update = $mysqli->prepare('UPDATE table SET xxx = ? WHERE yyy = ?');
$update->bind_param('ss', $x, $y);
$update->execute();
$mysqli->commit();
// here are the IDs that were updated
$select->bind_result($id);
while ($select->fetch()) printf('Updated id: %s\n', $id);
The only way I can think of is to first sleect rows that would be updated with the update statement, those are:
$updatableIds = mysqli_query("SELECT id FROM table WHERE xxx !='".$x."' AND yyy='".$y."'");
we add xxx !='".$x."' because if value of xxx already was $x those rows would not be affected.
Next you run the update
$sql = mysqli_query("update table set xxx='".$x."' where yyy='".$y."'");
UPDATE users
SET type = '3'
WHERE type = '2';
To find out the last affected row right after the statement, it should be slightly updated as follows:
UPDATE users
SET type = '3',
user_id=LAST_INSERT_ID(user_id)
WHERE type = '2';
// use function
function updateAndGetId($value)
{
$query ="UPDATE users
SET type = '$value',
user_id=LAST_INSERT_ID(user_id)
WHERE type = '2'";
mysql_query($query)
return mysql_insert_id();
}
$lastUpdatedRow = updateAndGetId(3);
In case you want to update only really changed row, add a conditional update of the user_id through the LAST_INSERT_ID and check if the data is going to change in the row.

insert values between rows

I don't know if it can be done with just a sql query or it needs a php code
when a cid is missing
There exist many missing values which I can't handle manually
For example, here I don't have cid=1 and cid=6.
I want to insert a row:
cid=1 tcp_sport='undefined' tcp_dport='undefined'
and
cid=6 tcp_sport='undefined' tcp_dport='undefined'
It seems to me I should create a procedure and insert between lines
another solution that I thaught was that I will create a table with cid and undifined values with the respective order and then join this one with that one and this join should have for example ifnull(tcp_sport,'')
would you please help me?
First, use MAX for get the largest ID.
SELECT MAX(cid) as max FROM table
Then, create a for loop for checking if the individual IDs exist:
for ($i = 0; $i < $max; $i++) {
// $query = ... SELECT 1 FROM table WHERE cid = $i ...
// check if the number of rows for $query is greater than 0
// if not, INSERT INTO table VALUES ($i, DEFAULT, DEFAULT)
}
The whole idea of an auto increment ID is to have a value that only refers to one thing ever. By "inserting between the lines" you may be opening yourself up to a lot of unforeseen problems. Image you have another table that has some values that link to the CID of this table. What if that table already has an entry for CID=1, When you insert a new item with CID=1 it will then join to that supporting record. So Data that really belongs to the original item with CID=1 will show for the new item which it probably has nothing to do with.
You aren't going to run out of ID values (if you are approaching the limit of integer, switch it to bigInt), don't re-use IDs if you can avoid it.
You need to use PHP to automate this.
<?php
$link = mysql_connect("localhost", "mysql_user", "mysql_password");
mysql_select_db("database", $link);
while($i < max_value_cid)//replace max_value_cid by the numeric maximum value of cid (SELECT MAX(cid) as max FROM table)
{
$result = mysql_query("SELECT * FROM `table` WHERE cid=".$i, $link);
if(mysql_num_rows($result) == 0)
mysql_query("INSERT INTO `table` VALUES ($i, NULL, NULL);", $link);
$i++;
}
?>
Do test the query on a sample set before execution and remember to backup the entire table, just-in-case.

Mysql Lock Tables 100% guarantee that only one user can edit?

mysql_query("LOCK TABLES `counter` WRITE;") or die(mysql_error());
$res = mysql_query("SELECT `unID` FROM `counter`;") or die(mysql_error());
$row = mysql_fetch_assoc($res);
$unID = $row['unID'] + 1;
mysql_query("UPDATE `counter` SET `unID`=`unID`+1;") or die(mysql_error());
mysql_query("UNLOCK TABLES;") or die(mysql_error());
This is 100% guarantee that $unID will be unique for every user who visit web ?
mysql table type: MyISAM
From the manual:
Only the session that holds the lock can access the table. No other session can access it until the lock is released.
So the answer is yes, the number will be unique.
Or, you can just create a sequence table:
CREATE TABLE sequence (id INT NOT NULL);
INSERT INTO sequence VALUES (0);
Then use this query to get the next id:
UPDATE sequence SET id=LAST_INSERT_ID(id+1);
You can fetch the id by using mysql_insert_id().
Btw, try to use PDO or mysqli.

how to get insert_id in a variable before insert query

I like to create a specific filename for a file that will be created by the input of the fields from the insert query. i like to have an unique key for that. this key consists of an user_id, a timestamp and at the end of this, it should be placed the generated insert_id from the insert query fired. it should be placed the auto_increment no. for the end of my generated variable. so the problem is, that i create a variable before the insert query fired so that this variable will be part of the insert query like:
$get_num = $db->query("SELECT COUNT (*) FROM tableA");
$num = $query->fetch_assoc();
$end = $num + 1;
$file_id = $id .".". time() .".". $end;
$insert = $db->query("INSERT INTO tableA ( file_id, a, b, c) VALUES('".$file_id."','".$a."','".$b."','".c."')");
Actually, forget what I wrote previously. You cannot count on COUNT working for you because what happens when a row is deleted? You will have duplicate values. The best bet for you is to first create the row, grab the insert_id, then UPDATE the file_id uing the function you previously described.
$uid = uniqid();
$insert = $db->query("INSERT INTO tableA ( file_id, a, b, c) VALUES('".$uid."','".$a."','".$b."','".c."')");
$file_id = $id .".". time() .".". mysql_insert_id();
$db->query("UPDATE tableA SET file_id='{$file_id}' WHERE file_id='{$uid}' LIMIT 1;");
In the end, you still have to use two queries anyway, so its not like this takes any more resources. Plus you aren't doing a COUNT operation anymore.
In other news, please be sure to read up on SQLi, depending on where your a,b,c variable are coming from.
This is a bad idea. Do your insert and then use LAST_INSERT_ID. Otherwise, as #AuthmanApatira noted, you could have the wrong id. The PHP for this is mysql_insert_id().
Also note that if your index column is auto_increment, you don't even need to worry about the id; the db takes care of it for you. You can just get it after your query runs.

How do I get all the ids of the row created by one multiple row insert statement

I'm new to php. So, please forgive me if this seems like a dumb question.
Say i have a MySQL insert statement insert into table (a,b) values (1,2),(3,4),(5,6). table 'table' has a auto increment field called 'id'.
how can I retrieve all the ids created by the insert statement above?
It will be great if i get an example that uses mysqli.
You can't. I would suggest that you maintain your own ids (using guid or your own auto-increment table) and use it when you insert into the table.
But it's possible to get the auto-increment value for the last inserted using LAST_INSERT_ID():
http://dev.mysql.com/doc/refman/5.0/en/getting-unique-id.html
AngeDeLaMort's answer is almost right. Certainly, the most appropriate way to deal with the problem is to insert one row at a time and poll the insert_id or generate the sequence elsewhere (which has additional benefits in terms of scalability).
I'd advise strongly against trying to determine the last insert_id and comparing this the most recent insert_id after the insert - there's just too may ways this will fail.
But...an alternative approach would be:
....
"INSERT INTO destn (id, data, other, trans_ref)
SELECT id, data, other, connection_id() FROM source";
....
"SELECT id FROM destn WHERE trans_ref=connection_id()";
....
"UPDATE destn SET trans_ref=NULL where trans_ref=connection_id()";
The second query will return the ids generated (note that this assumes that you use the same connection for all 3 queries). The third query is necessary because connection ids to go back into the pool when you disconnect (i.e. are reused).
C.
In some cases, if you have another identifier of sort such as a UserID, you could filter your query by UniqueID's greater than or equal to mysql_insert_id(), limit by the number of affected rows and only display those by the user. This would really only work inside of a transaction.
$SQL = "INSERT INTO Table
(UserID, Data)
VALUES
(1,'Foo'),
(1,'Bar'),
(1,'FooBar')";
$Result = mysql_query($SQL);
$LastID = mysql_insert_id();
$RowsAffected = mysql_affected_rows();
$IDSQL = "SELECT RecordID
FROM Table
WHERE UserID = 1
AND RecordID >= '$LastID'
LIMIT '$RowsAffected'";
$IDResult = mysql_query($IDSQL);
as a follow up to AngeDeLaMort:
You could seperate your inserts and do it something like this:
$data = array (
array(1,2),
array(3,4),
array(5,6)
);
$ids = array();
foreach ($data as $item) {
$sql = 'insert into table (a,b) values ('.$item[0].','.$item[1].')';
mysql_query ($sql);
$id[] = mysql_insert_id();
}
Now all your new id's are in the $id array.
Maybe I can do this
$insert = "insert into table (a,b) values (1,2),(3,4),(5,6)";
$mysqli->query($insert);
$rows_to_be_inserted=3;
$inserted_id = $mysqli->insert_id // gives me the id of the first row in my list
$last_row_id = ($inserted_id+$rows_to_be_inserted)-1;
$mysql->query("select * from table where id between $inserted_id and $last_row_id");
what to you guys say?

Categories