How to affectively lock/block rows? - mysql

I am using an api to develop an inventory system for a company. I want to be able to allow the first person to select a row(s) from the mysql database to have a lock. The second person should be denied any data from the set of rows the first user has. Is that even possible?
The use case, the information in the database is constantly being added or updated by users. If user A does a select it will always be followed by an update. But if user B selects the information updates it before user A is done, all the work from user B will be lost when user A is done or visa versa.
I have tried to use transactions but it is not stopping a second user from getting the row the first user requests.
start transaction;
select * from peak2_0.staff where 'First Name'='Aj';
update peak2_0.staff set `First Name` = 'aj' where 'First Name'='Aj';
commit;

As I mentioned in the comments, you can create a field (or two) for "locking" the entry while a user is working on it; more of a "down for maintenance" indicator of sorts, than an actual server lock. You can even make it atomic and recoverable with something like this:
UPDATE someTable
SET locked_by = client_or_user_id, locked_when = now()
WHERE [criteria for selected the record(s) being worked on]
AND locked_by IS NULL
;
You can then select from the table to see if it got your program client id or users id for the lock. "Recoverable" in the sense that, should the client system go down before unlocking the data, a routine process (client side, or MySql event), can release any locks older than a certain amount of time. Alternatively, the original update, and anything that is trying to respect locks can have the standard lock checking condition be tweaked to something like AND (locked_by IS NULL OR locked_when < now() - INTERVAL 15 MINUTE)
If an editing client needs to hold a lock for longer, it can do so just by updating locked_when values further; or you could also/alternatively use a "lock until" field.
Optionally, you could even add a lock reason so clients attempting to access such an entry can be informed why it is unavailable.

Related

Update SQL database via MS Access with an identical value gives a lock violation

EDIT: I've done a terrible job of explaining this. The short version:
If I try to update a table value in a MySQL table from MS Access with an identical value, I get a lock violation. Table1.Value1 contains "some_value". I update Table1.Value1 with "some_value" I get a lock error. Any other value works fine. I can also make the update in MySQL directly without issue. The error is only there when done from MS Access.
I have a mySQL database where I need to update 2 fields in different tables from MS Access. It is for the cancellation of a product.
The 2 fields are account.status and product.date
When a customer cancels an account, account.status should change to "Cancelled" and a date should pop up in product.date, the date is taken from account.date, and 30 days are added.
This works fine as long as account.status IS NOT "Cancelled" already. I cannot for the life of me figure out how to have MS Access ignore the lock violation I get then.
If account.status is anything but "Cancelled" it works just fine, but when updating the table with an identical value I get a lock violation.
UPDATE product INNER JOIN account ON product.cust_id = account.id SET product.date = [account].[date]+30, account.status = "Cancelled" WHERE (((account.id)=[Forms]![donotrenew kilde]![id]));

How to reset a column value in MySQL after it's been set to a certain value after 1 hour?

Hi I am quite new to MySQL events and triggers, I have a table called userstatus(userid, timestamp,count, flag) which is used to store data about the user if he's blocked or not. When count reaches 4, the flag becomes 0(blocked). Can you please help me on what I can do to automatically reset the flag to 1 after it remains blocked for an hour.
I have tried the following code:
create event testevent
on schedule
every 1 minute
starts CURRENT_TIMESTAMP
do
update demo.userstatus set flag=1
where timestampdiff(hour,timestamp,CURRENT_TIMESTAMP)>1
This seems to work but is not efficient. Any help would be appreciated. Thanks!
It would be a lot more efficient if you added a datetime field to the usersratus table that contains the date and time until a user is blocked, or use the existing timestamp field that stores when the user was blocked rather than having just a flag.
During login or any activity you deem to fit, you just check if the current time is on or earlier than the block time (or on or before timestamp + 1 hour). If yes, then the user is blocked and you can prevent the activity. If not, then the user is not blocked and can allow the activity. You can even restore the counter at the first successful activity to 1.
This way you avoid the need to periodically check the database and clear the status flag.

A safer way to delete data in MYSQL workbench than its Safe Mode

Here's the thing, I keep deleting rows in MySQL Workbench(this is the third time) because I usually open a tab for each table from where I need to view data, and I recycle that same tab(so as not to clog it), so let's say I open users tab, to view user id's 24's data, but then I need to see the data from user with ID 26, so I just replace the WHERE part to id = 26 or whatever, but sometimes I also do DELETE statements, but don't close the tab, so let's say a week later I want to look the user with id 28, I go to the users tab and just replace the WHERE part thinking it's a SELECT statement only to my horror to find it's a DELETE statement.
So, what can I do to safely delete data, for example for a popup to open saying do you really want to delete this? So I know it's a delete statement. Of course I could look the statement for if it's a SELECT or DELETE and I've tried, but I do a lot of SELECT statements and queries, that I want to see if there's a safer way. The safe mode inherent to the Workbench works for general delete statements but not specific ones(WHERE X). Maybe there's not an option like that in MySQL's Workbench, but there is in another alternative software? And yes, I'm stupid and careless, and try to put attention but I do a lot of queries every day and don't want to slip up again
A good way to handle this is to use transactions;
For example
delete from users where id = 2
As a safe guard you would then wrap this statement in a transaction that rolls back.
start transaction;
delete from users where id = 2
rollback;
Now the only way to delete the user would be to select only the second row or to change rollback to commit and run the entire query.
On another note you could write the above query to see changes in your transaction e.g.
start transaction;
select * from users where id = 2; # user exists
delete from users where id = 2;
select * from users where id = 2; # user gets deleted
rollback;

race condition in mysql select sql

What I try to accomplish seems simple,
Db type: MyISAM
Table Structure: card_id, status
Query: select an unused card_id from a table, and set the row as "used".
Is it race condition that when two queries running at the same time, and before status is updated, the same card_id is fetched twice?
I did some search already. It seems Lock table is a solution, but it's overkill to me and need Lock Privilege.
Any Idea?
Thanks!
It really depends on what statements you are running.
For plain old UPDATE statements against a MyISAM table, MySQL will obtain a lock on the entire table, so there is no "race" condition between two sessions there. One session will wait until the lock is released, and then proceed with it's own update (or will wait for a specified period, and abort with a "timeout".)
BUT, if what you are asking about is two sessions both running a SELECT against a table, to retrieve an identifier for a row to be updated, and both sessions retrieving the same row identifier, and then both sessions attempting to update the same row, then yes, that's a definite possibility, and one which really does have to be considered.
If that condition is not addressed, then it's basically going to be a matter of "last update wins", the second session will (potentially) overwrite the changes made by a previous update.
If that's an untenable situation for your application, then that does need to be addressed, either with a different design, or with some mechanism that prevents the second update from overwriting the update applied by the first update.
One approach, as you mentioned, is to avoid this situation by first obtaining an exclusive lock on the table (using a LOCK TABLES statement), then running a SELECT to obtain an identifier, and then running an UPDATE to update the identified row, and then finally, releasing the lock (using an UNLOCK TABLES statement.)
That's a workable approach for some low volume, low concurrency applications. But it does have some significant drawbacks. Of primary concern is reduced concurrency, due to the exclusive locks obtained on a single resource, which has the potential to cause a performance bottleneck.
Another alternative is an strategy called "optimistic locking". (As opposed to the previously described approach, which could be described as "pessimistic locking".)
For an "optimistic locking" strategy, an additional "counter" column is added to the table. Whenever an update is applied to a row in the table, the counter for that row is incremented by one.
To make use of this "counter" column, when a query retrieves a row that will (or might) be updated later, that query also retrieves the value of the counter column.
When an UPDATE is attempted, the statement also compares the current value of the "counter" column in the row with the previously retrieved value of the counter column. (We just include a predicate (e.g. in the WHERE clause) of the UPDATE statement. For example,
UPDATE mytable
SET counter = counter + 1
, col = :some_new_value
WHERE id = :previously_fetched_row_identifier
AND counter = :previously_fetched_row_counter
If some other session has applied an update to the row we are attempting to update (sometime between the time our session retrieved the row and before our session is attempting to do the update), then the value of the "counter" column on that row will have been changed.
The predicate on our UPDATE statement checks for that, and if the "counter" has been changed, that will cause our update to NOT be applied. We can then detect this condition (i.e. the affected rows count will be a 0 rather than a 1) and our session can take some appropriate action. ("Hey! Some other session updated a row we were intending to update!")
There are some good write-ups on how to implement an "optimistic locking" strategy.
Some ORM frameworks (e.g. Hibernate, JPA) provide support for this type of locking strategy.
Unfortunately, MySQL does NOT provide support for a RETURNING clause in an UPDATE statement, such as:
UPDATE ...
SET status = 'used'
WHERE status = 'unused'
AND ROWNUM = 1
RETURNING card_id INTO ...
Other RDBMS (e.g. Oracle) do provide that kind of functionality. With that feature of the UPDATE statement available, we can simply run the UPDATE statement to both 1) locate a row with status = 'unused', 2) change the value of status = 'used', and 3) return the card_id (or whatever columns we want) of the row the we just updated.
That gets around the problem of having to run a SELECT and then running a separate UPDATE, with the potential of some other session updating the row between our SELECT and our UPDATE.
But the RETURNING clause is not supported in MySQL. And I've not found any reliable way of emulating this type functionality from within MySQL.
This may work for you
I'm not entirely sure why I previously abandoned this approach using user variables (I mentioned above that I had played around with this. I think maybe I needed something more general, which would update more than one row and return a set of of id values. Or, maybe there was something that wasn't guaranteed about the behavior of user variables. (Then again, I only reference user variables in carefully constructed SELECT statements; I don't use user variables in DML; it may be because I don't have a guarantee of their behavior.)
Since you are interested in exactly ONE row, this sequence of three statements may work for you:
SELECT #id := NULL ;
UPDATE mytable
SET card_id = (#id := card_id)
, status = 'used'
WHERE status = 'unused'
LIMIT 1 ;
SELECT ROW_COUNT(), #id AS updated_card_id ;
It's IMPORTANT that these three statements run in the SAME database session (i.e. keep a hold of the database session; don't let go of it and get a new one.)
First, we initialize a user variable (#id) to a value which we won't confuse with a real card_id value from the table. (A SET #id := NULL statement would work as well, without returning a result, like the SELECT statement does.)
Next, we run the UPDATE statement to 1) find one row where status = 'unused', 2) change the value of the status column to 'used', and 3) set the value of the #id user variable to the card_id value of the row we changed. (We'd want that card_id column to be integer type, not character, to avoid any possible character set translation issues.)
Next, we run a query get the number of rows changed by the previous UPDATE statement, using the ROW_COUNT() function (we are going to need to verify that this is 1 on the client side), and retrieve the value of the #id user variable, which will be the card_id value from the row that was changed.
After I post this questions, I thought of a solution which is exactly the same as the one you mentioned at the end. I used update statement, which is "update TABLE set status ='used' where status = 'unused' limit 1", which returns the primary Id of the TABLE, and then I can use this primary ID to get cart_id. Says there are two update statements occurs at the same time, as you said, "MySQL will obtain a lock on the entire table, so there is no "race" condition between two sessions there", so this should solve my issue. But I am not sure why you said, "MySQL does NOT provide support an style statement".

Is there any alternative to "last_update_ID()" for mySQL?

I am currently working on a big web project using ASP and MySQL.
When inserting into multiple tables I've been using last_update_ID(), but after some research I've found that that SQL statement isn't safe.
So. the problem:
I use two different computers, with different internet connections.
Both computers are logged onto the system I am currently building. I have made a page that prints the connection_id(), and last_update_id.
If I update any table with one of the computers the other one also gets that last_update_ID.
Both computers have the same connection_ID.
What can I do to get around this?
I don't want to (if it's not necessary) do a select statement after the first INSERT; to search for the row that I inserted, to get the correct ID of that row.
It's not my server I am using so I can't make any large changes of the database.
I guess that this problem occurs because the webpages use the same loginName & password to connect to the database, is that true?
Is there any other alternative to get the last update ID? that is totally safe..
I close every connection at the end of the asp page. but that doesn't change the connection_ID.
The connection ID is the for a few minutes even thou I open up different web pages on the server.
I believe the LAST_INSERT_ID() is correct for the current session. So each session receives it's own correct value. Either I don't understand your question or you think you have a problem but you don't.
I am not aware of any LAST_UPDATE_ID() function, on an update you can easily retrieve the updated rows by SELECTing them with the same WHERE clause (before the update)?
reference: http://dev.mysql.com/doc/refman/5.0/en/getting-unique-id.html
For LAST_INSERT_ID(), the most
recently generated ID is maintained in
the server on a per-connection basis.
It is not changed by another client.
It is not even changed if you update
another AUTO_INCREMENT column with a
nonmagic value (that is, a value that
is not NULL and not 0). Using
LAST_INSERT_ID() and AUTO_INCREMENT
columns simultaneously from multiple
clients is perfectly valid. Each
client will receive the last inserted
ID for the last statement that
client executed.
If you want to retrieve the LAST_INSERT_ID from an INSERT query with an ON DUPLICATE KEY UPDATE clause, you can also use the LAST_INSERT_ID() function to retrieve the value of the AUTO_INCREMENT column that was updated:
reference: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
If a table contains an AUTO_INCREMENT
column and INSERT ... UPDATE inserts a
row, the LAST_INSERT_ID() function
returns the AUTO_INCREMENT value. If
the statement updates a row instead,
LAST_INSERT_ID() is not meaningful.
However, you can work around this by
using LAST_INSERT_ID(expr). Suppose
that id is the AUTO_INCREMENT column.
To make LAST_INSERT_ID() meaningful
for updates, insert rows as follows:
INSERT INTO table (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;
Your server appears to have connection pooling turned on. What this means is that the database connection is held open after a script finishes, and the next script that comes along uses it, and thus can see any variables that were set on that connection, including LAST_INSERT_ID().
What can't happen is two script instances sharing a connection at the same time. Thus, if your server is busy enough to need to run two script instances at exactly the same time, it will simply create a second database connection, with its own separate LAST_INSERT_ID() variable, and won't interfere with the first.
In short, as long as the INSERT and the LAST_INSER_ID() request happen within the same script (and you don't somehow close the database connection between them), they're completely safe, as your script has exclusive use of that connection.