SELECT & UPDATE at the same time - race condition - mysql

I'm experiencing a race condition because I'm dealing with a lot of concurrency.
I'm trying to combine these two mysql statements to execute at the same time.
I need to select a row and update the same one...
SELECT id_file FROM filenames WHERE pending=1 LIMIT 1;
UPDATE filenames SET pending=2 WHERE id_file=**id of select query**;
Another solution to the race-condition I'm experiencing would be to perform an UPDATE query where pending=1 and somehow get the ID of the updated row, but I'm not sure if that's even possible?
Thanks

To deal with concurrency is one of the basic functions of transactions.
Wrap your queries into one transaction and tell the DBMS, that you need the row not to change in between with FOR UPDATE:
BEGIN;
SELECT id_file FROM filenames WHERE pending=1 LIMIT 1 FOR UPDATE;
# do whatever you like
UPDATE filenames SET pending=2 WHERE id_file=**id of select query**;
COMMIT;
You can execute these statements with 4 mysqli_query calls, and do whatever you want in between, without need to worry about the consistency of your database. The selected row is save until you release it.

You can avoid the "race" condition by performing just an UPDATE statement on the table, allow that to identify the row to modified, and then subsequently retrieve values of columns from the row.
There's a "trick" returning values of columns, in your case, the value of the id_file column from the row that was just updated. You can use either the LAST_INSERT_ID() function (only if the column is integer type), or a MySQL user-defined variable.
If the value of the column you want to retrieve is integer, you can use LAST_INSERT_ID() function (which supports a BIGINT-64 value).
For example:
UPDATE filenames
SET pending = 2
, id_file = LAST_INSERT_ID(id_file)
WHERE pending = 1
LIMIT 1;
Following the successful execution of the UPDATE statement, you'll want to verify that at least one row was affected. (If any rows satisfied the WHERE, and the statement succeeded, we know that one row will be affected. Then you can retrieve that value, in the same session:
SELECT LAST_INSERT_ID();
to retrieve the value of id_file column of the last row processed by the UPDATE statement. Note that if the UPDATE processes multiple rows, only the value of last row that was processed by the UPDATE will be available. (But that won't be an issue for you, since there's a LIMIT 1 clause.)
Again, you'll want to ensure that a row was actually updated, before you rely on the value returned by the LAST_INSERT_ID() function.
For non-integer columns, you can use a MySQL user-defined variable in a similar way, assigning the value of the column to a user-defined variable, and then immediately retrieve the value stored in the user-defined variable.
-- initialize user-defined variable, to "clear" any previous value
SELECT #id_file := NULL;
-- save value of id_file column into user-defined variable
UPDATE filenames
SET pending = 2
, id_file = (SELECT #id_file := id_file)
WHERE pending = 1
LIMIT 1;
-- retrieve value stored in user-defined variable
SELECT #id_file;
Note that the value of this variable is maintained within the session. If the UPDATE statement doesn't find any rows that satisfy the predicate (WHERE clause), the value of the user-defined variable will be unaffected... so, to make sure you don't inadvertently get an "old" value, you may want to first initialize that variable with a NULL.
Note that it's important that a subsequently fired trigger doesn't modify the value of that user defined variable. (The user-defined variable is "in scope" in the current session.)
It's also possible to do the assignment to the user-defined variable within in a trigger, but I'm not going to demonstrate that, and I would not recommend you do it in a trigger.

Related

Is it possible to pass a value while inserting?

I have a trigger like this:
IF ( value > new.value ) THEN
...
END IF
It will be executed AFTER INSERT. I need to pass value (it is a number) while inserting because of using it into the condition of the trigger. Any idea how can I pass it?
Noted that, currently it is hardcoded into trigger like IF ( 5 > new.value ) THEN. And all I'm trying to do is passing that 5 dynamically while inserting.
No, there's no defined mechanism for passing an argument to a TRIGGER.
The only things I can think of is a user-defined variable (but I wouldn't really want to go there), or a SELECT statement to retrieve the value from a table.
We can set a user-defined variable in our session:
SET #value_for_after_insert_trigger = 5 ;
Then perform an INSERT
INSERT INTO mytable (...) VALUES (...)
when the AFTER INSERT trigger is fired (for each row), the trigger body can reference a user-defined variable. For example:
DECLARE ln_value INTEGER;
SET ln_value = #value_for_after_insert_trigger;
Then we can do
IF ( ln_value > NEW.value ) THEN
Note that we aren't guaranteed that some other statement in our session won't modify our user-defined variable. For example, a BEFORE INSERT trigger might execute a statement such as:
SET #value_for_after_insert_trigger = 42 ;
... overwriting the value that was previously stored. The AFTER INSERT trigger would read the currently assigned value, getting the 42, not the 5 we specified earlier. And we aren't guaranteed that the AFTER INSERT trigger won't perform some action that modifies the value.
SET #value_for_after_insert_trigger = #value_for_after_insert_trigger + 1;
affecting the trigger execution for subsequent rows.
I do not advocate this as a design. I would avoid using user-defined variables like this.
Another alternative would be to use a SELECT statement to query the value from a table.

Mysql update statement updated Unexpectedly

For updating MySql table, i used the query-
update TABLE set status=1 and finalStatus_id=1 and id in (1,3,4);
I mistakenly wrote and in place of where and execute the query.
As per query, I was expecting ERROR but it ran successfully. Secondly, I was expected to update status of all rows as 1.
But,
Status of all the rows got updated to 0.(Why this?)
I tried to find out the reason and smililar use of query but cant find.
Why is this? Why it behaved like this?
Your code is interpreted as:
update TABLE
set status = (1 and (finalStatus_id = 1) and (id in (1, 3, 4));
This is a boolean expression. It will only be "1" (true) when the id is 1, 3, or 4 and the value of finalStatus_id is 1. If all rows are set to 0, then this is never true.
The query you want is presumably:
update TABLE
set status = 1,
finalStatus_id = 1
where id in (1, 3, 4);
Commas are used to separate the updates to separate columns, not the boolean and.
In SQL, the WHERE clause is optional. It's legal syntax to update every row in the table, which you can do by omitting the WHERE clause.
UPDATE table SET status=1;
Maybe this is not a good language design, to make the default be that your update will apply to every row, but this standard SQL syntax, for better or worse.
The MySQL client has a flag --safe-updates which prevents you from executing such queries by accident. It treats a query as an error if it's an UPDATE or DELETE and has no WHERE clause. This only works in the MySQL client, not in any programmatic interface. So it doesn't work if you make the same mistake in application code.
The next part is that you set your status to an expression. An expression can be a simple value like 1, but it can also be a more complex expression.
This is a legal expression:
1 and finalStatus_id=1 and id in (1,3,4)
Since 1 is the same thing as true, it's a legal operand for and. The other two terms are also boolean conditions.
The result of the whole expressions is a boolean true or false, which in MySQL is equivalent to 1 or 0.
So you applied this expression to every row in your table. Certainly it will be false on every row where id is not in (1,3,4).

How to check if a particular value exists in a variable which stores multiple value in mysql?

I am trying to create an sql trigger statement using phpmyadmin trigger interface.
Trying to do something for table 1 as shown below :
BEGIN
declare #valid_number int ;
select id into #valid_number from table 2 ;
if 10 does not exist in #valid_number then
{do something here}
end if;
END
how to achieve it?
First: a variable in a stored routine can't store multiple values, just a single one. Your statement
select id into #valid_number from table 2 ;
will only work, if the query returns exactly one row. An error will occur, if the query returns multiple rows, a warning, if the query returns no row at all, see the manual page to SELECT ... INTO:
The INTO clause can name a list of one or more variables, which can be
user-defined variables, stored procedure or function parameters, or
stored program local variables. [...]
The selected values are assigned to the variables. The number of
variables must match the number of columns. The query should return a
single row. If the query returns no rows, a warning with error code
1329 occurs (No data), and the variable values remain unchanged. If
the query returns multiple rows, error 1172 occurs (Result consisted
of more than one row).
Solution:
It's not difficult to create a statement that gives you the desired answer in exact one row, i.e.
SELECT COUNT(*) into valid_number FROM example WHERE id = 10;
This query will return 0, if the id 10 does not exists in column id and the count of occurences else. Of course there are several ways to achieve this, this is just one of them. You could rewrite your stored routine to:
BEGIN
-- prefer local variables, don't use user defined, if not needed.
DECLARE valid_number int;
SELECT COUNT(*) into valid_number FROM example WHERE id = 10;
IF valid_number = 0 THEN
-- do something here
END IF;
SELECT result;
END
Note
You could use a cursor to traverse the result of a query, but most times one wants to avoid a cursor. To use a cursor under similar conditions as of this question would not be the SQL way to do it and most times very inefficient.

Mysql increment in loop by variable

I am trying to use a simple MySQL query to update my table with positions.
Let's assume I have a book table with an ID, a writer_id and a position field.
I want to be able to have positions from 1 ... x , but per writer_id.
If I delete a record there will be a gap in my positions so this is why I want a simple query to reset all the positions without gaps.
Currently I have the following code (which works), but I think this should be possible a lot easier (and probably faster).
set #position := 0;
set #lastDependency := 0;
set #previousDependency := -1;
UPDATE `book` SET
`writer_id`=(#lastDependency:=`writer_id`), -- Set writer_id of current row
position=(
IF (
NOT #lastDependency=#previousDependency,
#position:=1, -- New writer_id => set position to 1
#position:=#position+1 -- Same writer id, increment position
)
),
`writer_id`=(#previousDependency:=`writer_id`) -- Set writer_id of last used row
ORDER BY `writer_id`, position ASC -- Order by current positions
I can also use PHP to loop through all my records and save them one by one, but I guess that won't be any better
why don't you use a trigger with the following function:
"when a row is deletet, reduce evere writer_id that is greater than the deleted ones by one"
or to say it in pseudo-code:
create trigger for delete...
update book
set writer_id = writer_id - 1
where writer_id > deleted.writer_id
Let me quote the MySQL documentation to you:
As a general rule, you should never assign a value to a user variable and read the value within the same statement. You might get the results you expect, but this is not guaranteed. The order of evaluation for expressions involving user variables is undefined and may change based on the elements contained within a given statement; in addition, this order is not guaranteed to be the same between releases of the MySQL Server.
So the way you attempt to do things may work, but comes with absolutely no guarantees. Therefore I'd suggest you do this in PHP instead of MySQL. Or you do it in MySQL using a stored procedure.

MySQL UPDATE and SELECT in one pass

I have a MySQL table of tasks to perform, each row having parameters for a single task.
There are many worker apps (possibly on different machines), performing tasks in a loop.
The apps access the database using MySQL's native C APIs.
In order to own a task, an app does something like that:
Generate a globally-unique id (for simplicity, let's say it is a number)
UPDATE tasks
SET guid = %d
WHERE guid = 0 LIMIT 1
SELECT params
FROM tasks
WHERE guid = %d
If the last query returns a row, we own it and have the parameters to run
Is there a way to achieve the same effect (i.e. 'own' a row and get its parameters) in a single call to the server?
try like this
UPDATE `lastid` SET `idnum` = (SELECT `id` FROM `history` ORDER BY `id` DESC LIMIT 1);
above code worked for me
You may create a procedure that does it:
CREATE PROCEDURE prc_get_task (in_guid BINARY(16), OUT out_params VARCHAR(200))
BEGIN
DECLARE task_id INT;
SELECT id, out_params
INTO task_id, out_params
FROM tasks
WHERE guid = 0
LIMIT 1
FOR UPDATE;
UPDATE task
SET guid = in_guid
WHERE id = task_id;
END;
BEGIN TRANSACTION;
CALL prc_get_task(#guid, #params);
COMMIT;
If you are looking for a single query then it can't happen. The UPDATE function specifically returns just the number of items that were updated. Similarly, the SELECT function doesn't alter a table, only return values.
Using a procedure will indeed turn it into a single function and it can be handy if locking is a concern for you. If your biggest concern is network traffic (ie: passing too many queries) then use the procedure. If you concern is server overload (ie: the DB is working too hard) then the extra overhead of a procedure could make things worse.
I have the exact same issue. We ended up using PostreSQL instead, and UPDATE ... RETURNING:
The optional RETURNING clause causes UPDATE to compute and return value(s) based on each row actually updated. Any expression using the table's columns, and/or columns of other tables mentioned in FROM, can be computed. The new (post-update) values of the table's columns are used. The syntax of the RETURNING list is identical to that of the output list of SELECT.
Example: UPDATE 'my_table' SET 'status' = 1 WHERE 'status' = 0 LIMIT 1 RETURNING *;
Or, in your case: UPDATE 'tasks' SET 'guid' = %d WHERE 'guid' = 0 LIMIT 1 RETURNING 'params';
Sorry, I know this doesn't answer the question with MySQL, and it might not be easy to just switch to PostgreSQL, but it's the best way we've found to do it. Even 6 years later, MySQL still doesn't support UPDATE ... RETURNING. It might be added at some point in the future, but for now MariaDB only has it for DELETE statements.
Edit: There is a task (low priority) to add UPDATE ... RETURNING support to MariaDB.
I don't know about the single call part, but what you're describing is a lock. Locks are an essential element of relational databases.
I don't know the specifics of locking a row, reading it, and then updating it in MySQL, but with a bit of reading of the mysql lock documentation you could do all kinds of lock-based manipulations.
The postgres documenation of locks has a great example describing exactly what you want to do: lock the table, read the table, modify the table.
UPDATE tasks
SET guid = %d, params = #params := params
WHERE guid = 0 LIMIT 1;
It will return 1 or 0, depending on whether the values were effectively changed.
SELECT #params AS params;
This one just selects the variable from the connection.
From: here