Incorrect data in MyISAM database due to concurrency - php

Problem
I have a webpage that does the following (the code is much simplified to show only relevant code.
mysql_query("insert into table1 (field1) values ('value')");
$last_id = mysql_insert_id();
$result = mysql_query("select * from table1 t inner join ... where id = $last_id");
write_a_file_using_result($result);
It happened, that the file was created using a different data set than what I found in the table row.
The only explanation I have is:
call1: page was called 1. time with data set 1.
call1: data set 1 gets inserted for connection 1 but gets not committed to the table.
call2: page was called 2. time with data set 2
call2: data set 2 gets inserted for connection 2 and mysql_insert_id returns the same value
call1: file is generated with date set 1
call2: file cannot be written, because it already exists
Result: The file is generated with data set 1 while the table row contains data row 2.
Config
mysql 5.0.51b
The table:
CREATE TABLE `table1` (
`id` int(11) NOT NULL auto_increment,
(...)
Question
I know that MyISAM does not support transactions. But I really expect that it is impossible to insert two rows and get twice the same id inserted, so that the row can be overwritten.
Is MyISAM unsafe to this point or is there another explanation that I overlook ?
Note
I know the mysql extension for php is outdated, but I did not yet rewrite the application.

Is MyISAM unsafe to this point
No. mysql_insert_id guaranteed to return the right value only.
or is there another explanation that I overlook ?
Most likely. Check your code.

Haven't heard about id issues in MyISAM.
You can try to set link identifier when calling last_insert_id, for example
$link = mysql_connect(...);
mysql_query("insert into table1 (field1) values ('value')",$link);
$last_id = mysql_insert_id($link);
$result = mysql_query("select * from table1 t inner join ... where id = $last_id",$link);
write_a_file_using_result($result);

Related

Common syntax in MySQL and MSSQL for IF INSERT ELSE UPDATE

I have a PHP 7.3 project which connects via PDO to a MySQL database or a MSSQL database, depending on being run on Linux or Windows.
I want to insert a new values into a table, if the unique value is not yet in that table. If it is already in the table, I want to update the non-unique values.
I searched a lot of docs and SO posts, also, but I couldn't find a syntax, which does that in one query for both database types.
SQL Server query:
IF (EXISTS (SELECT * FROM failed_logins_ip_address WHERE ip_address = 'xxx'))
BEGIN
UPDATE failed_logins_ip_address
SET attempts_count = attempts_count + 1, attempt_datetime = CURRENT_TIMESTAMP
WHERE ip_address = 'xxx'
END
ELSE
BEGIN
INSERT INTO failed_logins_ip_address (ip_address, attempts_count, attempt_datetime)
VALUES ('xxx', 1, CURRENT_TIMESTAMP)
END
MySQL query:
INSERT INTO failed_logins_ip_address (ip_address, attempts_count, attempt_datetime)
VALUES ('xxx', 1, CURRENT_TIMESTAMP)
ON DUPLICATE KEY
UPDATE attempts_count = attempts_count + 1, attempt_datetime = CURRENT_TIMESTAMP
'ip_addess' column is unique, and the table structure is identical for both MSSQL and MySQL.
Is there a syntax, which can do an IF INSERT ELSE UPDATE in both database types?
Yes, I do (PDO) parameter binding, xxx is just to shorten the code snippet.
Yes, I could use identical syntax if I did it in two queries (first select, then insert or update) but I want to avoid (hopefully) unnecessary queries.
No, I do not want to insert every login attempt so I do not need the update anymore because I do not need this data.
If the REPLACE approach would work: this does not update, it deletes and inserts, which I also do not want.
My current solution: I check in PHP for the current database type and switch/case the query strings. It is clean but one string is even less smelly ;-)
UPDATE:
I changed the MSSQL query around: from of IF NOT EXISTS TO IF EXISTS to improve the efficiency. UPDATE will occur a lot more often than INSERT, so in most of the cases, only the first (sub)query will be executed.
After digging deeper, I found this post by a Derek Dieter, which describes how to replace SQL Server's IF EXISTS ELSE by WHERE EXISTS:
https://sqlserverplanet.com/optimization/avoiding-if-else-by-using-where-exists
The WHERE EXISTS syntax seems to be the same in MySQL and MSSQL.
Derek Dieter's example, with IF EXSISTS:
IF NOT EXISTS (SELECT 1 FROM customer_totals WHERE cust_id = #cust_id)
BEGIN
INSERT INTO customer_totals
(
cust_id,
order_amt
)
SELECT
cust_id = #cust_id
,order_amt = #order_amt
END
ELSE
UPDATE customer
SET order_amt = order_amt + #order_amt
WHERE cust_id = #cust_id
END
Derek Dieter's example, with WHERE EXISTS:
INSERT INTO customer_totals
(
cust_id,
order_amt
)
SELECT TOP 1 — important since we’re not constraining any records
cust_id = #cust_id
,order_amt = #order_amt
FROM customer_totals ct
WHERE NOT EXISTS — this replaces the if statement
(
SELECT 1
FROM customer_totals
WHERE cust_id = #cust_id
)
SET #rowcount = ##ROWCOUNT — return back the rows that got inserted
UPDATE customer
SET order_amt = order_amt + #order_amt
WHERE #rowcount = 0
AND cust_id = #cust_id — if no rows were inserted, the cust_id must exist, so update
I still have to test it, though, in MySQL. I'll update this post and add the code, if it works.
If you are using PHP, then you are calling the code through an interface. You can do the following:
Create a unique index on ip_address.
Attempt to insert a new row. This will fail if the row already exists.
If the insert fails (particularly with a duplicate key error), then update the existing row.
However, your goal of trying to have the same code in both databases is . . . just not going to work very well. The two databases are rather different. Perhaps you should consider constructing stored procedures in each database to do what you want and then calling those stored procedures.

Insert distinct records in the table while updating the remaining columns

This is actually a form to update the team members who work for a specific client, When i deselect a member then it's status turns to 0.
I have a table with all unique records. table consists of four columns -
first column is `id` which is unique and auto_incremented.
second column is `client_id`.
third column is `member_id`. (these second and third columns together make the primary key.)
fourth column is `current` which shows the status (default is 1.).
Now i have a form which sends the values of client_id and member_id. But this forms also contains the values that are already in the table BUT NOT ALL.
I need a query which
(i) `INSERT` the values that are not already in the table,
(ii) `UPDATE` the `current` column to value `0` which are in the table but not in the form values.
here is a screenshot of my form.
If (select count(*) from yourtable where client_id = and member_id = ) > 0 THEN
update yourtable set current = 0;
ELSE
insert into yourtable (client_id,member_id,current) values (value1,value2,value3)
First of all check if the value exists in the table or not, by using a SELECT query.
Then check if the result haven't save value so it will be inserted, else show an error .
This would be a great time to create a database stored procedure that flows something like...
select user
if exists update row
else insert new row
stored procedures don't improve transaction times, but they are a great addition to any piece of software.
If this doesn't solve your problem then a database trigger might help out.
Doing a little research on this matter might open up some great ideas!
Add below logic in your SP
If (select count(*) from yourtable where client_id = <value> and member_id = <value>) > 0 THEN
update yourtable set current = 0;
ELSE
insert into yourtable (client_id,member_id,current) values (value1,value2,value3)
if you want simple solution then follow this:
*) use select with each entry in selected team.
if select returns a row
then use update sql
else
use insert sql.
In your case member_id & client_id together makes the primary key.
So , you can use sql ON DUPLICATE KEY UPDATE Syntax.
Example:
$sql="INSERT INTO table_name SET
client_id='".$clientId."',
member_id='".$member_id."',
current='".$current."'
ON DUPLICATE KEY
UPDATE
current = '".$current."'
";
In this case when member_id & client_id combination repeats , it will automatically executes update query for that particular row.

MySQL update query in PHP and get multiple ids returned

I want to perform a mysql UPDATE query and then get an array of ids that were effected in the change.
This is my update query
mysql_query("UPDATE table SET deleted='1' WHERE id='$id' OR foo='$foo' OR bar='$bar'");
I know that I can do something like this to get the created id from an INSERT query
mysql_query("INSERT INTO table (id,foo,bar) VALUES ('$id','$foo','$bar')");
$newid = mysql_insert_id();
I don't think MySQL has anything like the OUTPUT or RETURNING clauses that other databases support. You can get the list of ids by running a select before the update:
create table temp_table ids_to_update as
SELECT id
FROM table
WHERE (deleted <> '1' or deleted is null) and *id='$id' OR foo='$foo' OR bar='$bar');
Note that MySQL doesn't do an update when the value doesn't change. Hence the first condition -- which you may or may not find important.
Then, to ensure integrity (in the event of intervening transactions that change the data), you can do:
update table t join
temp_table tt
on t.id = tt.id
set deleted = '1';
You could also wrap the two queries in a single transaction, but I think using a temp table to store the ids is probably easier.

PHP Code for fetching one row in MSSQL in each registration process

Here is my query part of my registration PHP form.
columns account,password,email and age could be inserted by registration page user and they work well but, the column account_id needs to be increased by 1 automatically with each registration process.
Table name is Account not account and column name is account_id.
$query = "INSERT Account( account,password,email,pk_,type_ ) VALUES('$username','$converted_password','$email',1,'$age')";
$query_total = mssql_query("SELECT COUNT(account_id) FROM Account");
$results_check = mssql_query($query_check);
$results_total = mssql_fetch_row($query_total);
$result_total = $results_total['0'];
It gives me a NULL value for the (account_id) column and INSERT fails.
Perform the following query on your database: (Mysql based query!! not Mssql!!)
ALTER TABLE `Account` CHANGE `account_id` `account_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY;
this wil result in an autoincrement.
If this fails you'll probably already own duplicates, you'd have to solve this first. There are many ways for this, though it mostly depends upon connections with other tables.
After that for each insert into account do not include the account_id.
use the mysql query:
select LAST_INSERT_ID();
to retrieve the last inserted id.
within PHP you can use http://nl1.php.net/mysql_insert_id though i'd highly advice you to start looking into http://www.php.net/PDO or http://www.php.net/mysqli with prepared statements.
Because as far as i've understood in the next version of PHP the basic Mysql functions will become deprecated. And prepared statements are better/safer. (If properly used)
Set account_id to auto increment in MySQL and just don't shoot anything to the MySQL database for the field account_id. MySQL will automatically create a new ID.
Read something about auto increment:
http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html
*edit:
Also change
$query = "INSERT Account( account,password,email,pk_,type_ ) VALUES('$username','$converted_password','$email',1,'$age')";
to
$query = "INSERT INTO Account(account,password,email,pk_,type_) VALUES('$username','$converted_password','$email',1,'$age')";

How to get ID of the last updated row in MySQL?

How do I get the ID of the last updated row in MySQL using PHP?
I've found an answer to this problem :)
SET #update_id := 0;
UPDATE some_table SET column_name = 'value', id = (SELECT #update_id := id)
WHERE some_other_column = 'blah' LIMIT 1;
SELECT #update_id;
EDIT by aefxx
This technique can be further expanded to retrieve the ID of every row affected by an update statement:
SET #uids := null;
UPDATE footable
SET foo = 'bar'
WHERE fooid > 5
AND ( SELECT #uids := CONCAT_WS(',', fooid, #uids) );
SELECT #uids;
This will return a string with all the IDs concatenated by a comma.
Hm, I am surprised that among the answers I do not see the easiest solution.
Suppose, item_id is an integer identity column in items table and you update rows with the following statement:
UPDATE items
SET qwe = 'qwe'
WHERE asd = 'asd';
Then, to know the latest affected row right after the statement, you should slightly update the statement into the following:
UPDATE items
SET qwe = 'qwe',
item_id=LAST_INSERT_ID(item_id)
WHERE asd = 'asd';
SELECT LAST_INSERT_ID();
If you need to update only really changed row, you would need to add a conditional update of the item_id through the LAST_INSERT_ID checking if the data is going to change in the row.
This is officially simple but remarkably counter-intuitive. If you're doing:
update users set status = 'processing' where status = 'pending'
limit 1
Change it to this:
update users set status = 'processing' where status = 'pending'
and last_insert_id(user_id)
limit 1
The addition of last_insert_id(user_id) in the where clause is telling MySQL to set its internal variable to the ID of the found row. When you pass a value to last_insert_id(expr) like this, it ends up returning that value, which in the case of IDs like here is always a positive integer and therefore always evaluates to true, never interfering with the where clause. This only works if some row was actually found, so remember to check affected rows. You can then get the ID in multiple ways.
MySQL last_insert_id()
You can generate sequences without calling LAST_INSERT_ID(), but the
utility of using the function this way is that the ID value is
maintained in the server as the last automatically generated value. It
is multi-user safe because multiple clients can issue the UPDATE
statement and get their own sequence value with the SELECT statement
(or mysql_insert_id()), without affecting or being affected by other
clients that generate their own sequence values.
MySQL mysql_insert_id()
Returns the value generated for an AUTO_INCREMENT column by the
previous INSERT or UPDATE statement. Use this function after you have
performed an INSERT statement into a table that contains an
AUTO_INCREMENT field, or have used INSERT or UPDATE to set a column
value with LAST_INSERT_ID(expr).
The reason for the differences between LAST_INSERT_ID() and
mysql_insert_id() is that LAST_INSERT_ID() is made easy to use in
scripts while mysql_insert_id() tries to provide more exact
information about what happens to the AUTO_INCREMENT column.
PHP mysqli_insert_id()
Performing an INSERT or UPDATE statement using the LAST_INSERT_ID()
function will also modify the value returned by the mysqli_insert_id()
function.
Putting it all together:
$affected_rows = DB::getAffectedRows("
update users set status = 'processing'
where status = 'pending' and last_insert_id(user_id)
limit 1"
);
if ($affected_rows) {
$user_id = DB::getInsertId();
}
(FYI that DB class is here.)
This is the same method as Salman A's answer, but here's the code you actually need to do it.
First, edit your table so that it will automatically keep track of whenever a row is modified. Remove the last line if you only want to know when a row was initially inserted.
ALTER TABLE mytable
ADD lastmodified TIMESTAMP
DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP;
Then, to find out the last updated row, you can use this code.
SELECT id FROM mytable ORDER BY lastmodified DESC LIMIT 1;
This code is all lifted from MySQL vs PostgreSQL: Adding a 'Last Modified Time' Column to a Table and MySQL Manual: Sorting Rows. I just assembled it.
Query :
$sqlQuery = "UPDATE
update_table
SET
set_name = 'value'
WHERE
where_name = 'name'
LIMIT 1;";
PHP function:
function updateAndGetId($sqlQuery)
{
mysql_query(str_replace("SET", "SET id = LAST_INSERT_ID(id),", $sqlQuery));
return mysql_insert_id();
}
It's work for me ;)
SET #uids := "";
UPDATE myf___ingtable
SET id = id
WHERE id < 5
AND ( SELECT #uids := CONCAT_WS(',', CAST(id AS CHAR CHARACTER SET utf8), #uids) );
SELECT #uids;
I had to CAST the id (dunno why)... or I cannot get the #uids content (it was a blob)
Btw many thanks for Pomyk answer!
Hey, I just needed such a trick - I solved it in a different way, maybe it'll work for you. Note this is not a scalable solution and will be very bad for large data sets.
Split your query into two parts -
first, select the ids of the rows you want to update and store them in a temporary table.
secondly, do the original update with the condition in the update statement changed to where id in temp_table.
And to ensure concurrency, you need to lock the table before this two steps and then release the lock at the end.
Again, this works for me, for a query which ends with limit 1, so I don't even use a temp table, but instead simply a variable to store the result of the first select.
I prefer this method since I know I will always update only one row, and the code is straightforward.
ID of the last updated row is the same ID that you use in the 'updateQuery' to found & update that row. So, just save(call) that ID on anyway you want.
last_insert_id() depends of the AUTO_INCREMENT, but the last updated ID not.
My solution is , first decide the "id" ( #uids ) with select command and after update this id with #uids .
SET #uids := (SELECT id FROM table WHERE some = 0 LIMIT 1);
UPDATE table SET col = 1 WHERE id = #uids;SELECT #uids;
it worked on my project.
Further more to the Above Accepted Answer
For those who were wondering about := & =
Significant difference between := and =, and that is that := works as a variable-assignment operator everywhere, while = only works that way in SET statements, and is a comparison operator everywhere else.
So SELECT #var = 1 + 1; will leave #var unchanged and return a boolean (1 or 0 depending on the current value of #var), while SELECT #var := 1 + 1; will change #var to 2, and return 2.
[Source]
If you are only doing insertions, and want one from the same session, do as per peirix's answer. If you are doing modifications, you will need to modify your database schema to store which entry was most recently updated.
If you want the id from the last modification, which may have been from a different session (i.e. not the one that was just done by the PHP code running at present, but one done in response to a different request), you can add a TIMESTAMP column to your table called last_modified (see http://dev.mysql.com/doc/refman/5.1/en/datetime.html for information), and then when you update, set last_modified=CURRENT_TIME.
Having set this, you can then use a query like:
SELECT id FROM table ORDER BY last_modified DESC LIMIT 1;
to get the most recently modified row.
No need for so long Mysql code. In PHP, query should look something like this:
$updateQuery = mysql_query("UPDATE table_name SET row='value' WHERE id='$id'") or die ('Error');
$lastUpdatedId = mysql_insert_id();

Categories