How to solve Concurrency issue? - php

All,
I am using MySql 5.7 in my application. I trying to make my save function Concurrency Safe. I will explain with an example.
Example :
I have two admin users Admin 1 and Admin 2. We have a product table and we have a product table entry with product code "P1". Suppose Admin 1 and Admin 2 are logged into the system and try to update product entry with code "P1" at the same time.
I need to inform one of the users that the record(s) you are trying modify is updating by another user and try again after some time.
I am using transaction and didn't change MySql's default transaction level(repeatable read). I am trying to solve it by using "SELECT FOR UPDATE"(included a where condition to check with modified time). This "where" condition will solve concurrency issue to those transactions which are already committed. But if two transaction starts at the same time and the first transaction gets committed before lock timeout, then when the second transaction executes, it overwrites the first one.
Kindly share your ideas
Thanks in advance

Well there are actually 2 issues here.
First, one of the admins will get a lock on the row before the other, so assuming admin1 gets the lock first, admin2 will queue until admin1's transaction completes, then admin2's transaction will take place.
So that is all looked after for you by the DBMS.
But the second issue is of course if both admin1 and admin2 are attempting to update the same column(s). In this case admin1's update will be overwritten by admin2's update. The only way to stop this happening if that is what you want to stop is to make the UPDATE very specific about what it is updating. In other words the UPDATE must be something like this
UPDATE table SET col1 = 'NewValue'
WHERE usual criteria
AND col1 = 'Its Original Value'
So this means that when you present the original data from this row to the user in a form, you must somehow remember what its original state was as well as capture its new state that the admin changed it to.
Of course the PHP code will also have to be written to capture the fact the UPDATE did not take place and return something to whichever admin's update has now failed. Showing the new value in the column in question and giving them a notice that the update failed because someone else already changed that field, and letting them either forget there change, or apply their change over the top of the other admins update.

There is this technique you can use to control it without locking the table or controlling the update you should do.
Create a field on your table that will be a version for that registry:
alter table someTable add column version not null integer default 0;
There will be no need to change any insert code with this.
Every time a user fetches a registry to update you make sure that it will have the version also in the object (or form,m or whatever way you handle your entity in the system).
Then you will need to Create a before update trigger for your table that will check if the version of the current registry is still the same is so you update if not you raise an error. Something like:
delimiter $$
create trigger trg_check_version BEFORE UPDATE
ON yourTable
for each row
begin
if NEW.version != OLD.version then
signal sqlstate '45000' set message_text = 'Field outdated';
else
NEW.version = NEW.version + 1;
end if;
end$$
delimiter;
Then you handle the error in your php code. This signal command will only work on MySql 5.5 or later. Check out this thread

Related

How to check in real-time if new row was added to MySQL table

We have an automatic car plate reader which records plates of the cars enter to firm. My colleague asked me if we can instantly get the plate number of the car coming. The software uses MySQL and I have only database access. Cannot reach/edit PHP codes.
My offer is to check using a query periodically. For example for 10 seconds. But in this way it is possible to miss the cars coming in 5 seconds. Then decreasing interval increases request/response count which means extra load for the server. I do not want the script to run always. It should run only a new db row added. It shows the plate and exits.
How can I get last recorded row from the db right after inserting? I mean there should be trigger which runs my PHP script after insertion. But I do not know.
What I want is MySQL could run my PHP script after a new record.
If your table is MyISAM, I would stick to your initial idea. Getting the row count from a MyISAM table is instant. It only takes the reading of one single value as MyISAM maintains the row count at all times.
With InnoDB, this approach can still be acceptable. Assuming car_table.id is primary key, SELECT COUNT(id) FROM car_table only requires an index scan, which is very fast. You can improve on this idea by adding another indexed boolean column to your table:
ALTER car_table ADD COLUMN checked BOOLEAN NOT NULL DEFAULT 0, ADD INDEX (checked);
The default value ensures new cars will be inserted with this flag set to 0 without modifying the inserting statement. Then:
BEGIN TRANSACTION; -- make sure nobody interferes
SELECT COUNT(checked) FROM car_table WHERE checked = FALSE FOR UPDATE; -- this gets you the number of new, unchecked cars
UPDATE car_table SET checked = TRUE WHERE checked = FALSE; -- mark these cars as checked
COMMIT;
This way, you only scan a very small number of index entries at each polling.
A more advanced approach consists in adding newly created cars ID's into a side table, through a trigger. This side table is scanned every now and then, without locking the main table, and without altering its structure. Simply TRUNCATE this side table after each polling.
Finally, there is the option of triggering a UDF, as suggested by Panagiotis, but this seems to be an overkill in most situations.
Although this is not the greatest of designs and I have not implemented it, there is way to call an external script through sys_exec() UDF using a trigger as mentioned here:
B.5.11: Can triggers call an external application through a UDF?
Yes. For example, a trigger could invoke the sys_exec() UDF.
http://dev.mysql.com/doc/refman/5.1/en/faqs-triggers.html#qandaitem-B-5-1-11
Also have a look on this thread which is similar to your needs.
Invoking a PHP script from a MySQL trigger

Mysql trigger variable - find updating user

I have table with fields:
id
data1
data2
next I want to set trigger that after update would write to log table changes:
CREATE TRIGGER `update_data` AFTER UPDATE on `data_table`
FOR EACH ROW
BEGIN
IF (NEW.data1 != OLD.data1) THEN
INSERT INTO data_tracking set old_value = OLD.data1, new_value = NEW.data1, field = "data1";
END IF;
-- similar for data2
END$$
I also want to record in data_tracking table user that made change, however this user is not part of original UPDATE that trigger the trigger. I it a way to let trigger know what user need to be recorded ?
This is PHP based web service with multiple registered users, that can make changes to record via website - those user i would like to add to trigger.
As you want to use the user name that only is known to PHP, well, MySql cannot know which user triggered the change if this information is only available in PHP. This means you would at least have to pass the user in every update statement, either adding this as a column to all tables that need this kind of trigger, or do all the updates via stored procedures that get the user name as an additional parameter. Then you could get rid of all the triggers, as you would use stored procedures anyway that can do the logging as well as the updates.
Use CURRENT_USER, see the MySQL manual: http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_current-user

Teething bug in system MySQL

In an old legacy based system we make updates to a users inventory. The inventory contains many different items, user will have one row per item id and in each row is a quantity of this item that they own.
Now somewhere in this rather old and behemoth like code is a problem whereby a user can end up with a minus quantity of an item. This should never happen.
Rather than approaching the problem from the top and going through each piece of code that interacts with the inventory table we thought we might try and create some reporting to help us find the problems.
Before I go about implementing something that I think may solve this problem I thought i'd put it out there to the community to find out how they might approach it.
Perhaps could start by creating on update MySQL rules which insert activities into another table for closer inspection etc. Be creative.
If you add a timestamp field then you'll know when the last operation was carried out - from that, you could find te update entry in the mysql log and possibly reconcile with the application logs.
Alternatively you could set a trigger on the table...
CREATE TRIGGER no_negatives_in_yourtable
BEFORE UPDATE ON yourtable
FOR EACH ROW
BEGIN
IF (NEW.value<0) THEN
/* log it (NB will be rolled back if subsequent statement enabled */
INSERT INTO badthings (....) VALUES (...);
/* this forces the operation to fail */
DROP TABLE `less than zero value in yourtable`;
END IF;
END

Could one user updates while other users cannot select

Is there a way in MySQL and PHP to allow only the person performing an update to view information about a particular record?
For example, if one user loads the page they are presented with a record which must be updated, until that user finishes updating this record, any other user accessing this page should not be able to view this particular record.
You'll have to manually lock the row (by adding a IsLocked column) when someone requests the edit page, since the connection to the database is lost as soon as the PHP script execution ends (despite pooling et al, script execution is stopped so you cannot unlock from the same thread again since that connection may go to another script).
Don't forget to create a kind of unlocking script, initiated by cron for example, to unlock rows that have been locked for more than a given amount of time.
I don't recommend adding any column to data table in question. I'd rather create special locks table to hold the information:
create table locks (
tablename varchar,
primarykey int,
userid int,
locktime datetime);
Then take the following principles into consideration:
each PHP request is a standalone mysql connection, that's why solutions like SELECT ... FOR UPDATE won't work and that's why you need to keep userid of a person - who actually did first request and performed the lock
every access to locks table must lock the table as a whole (using MySQL's LOCK statement) to avoid concurrency in locking the same row
if there is no way to know, whether particular uses has abandoned editing the row by closing the window with record - then either locktime timeout must be short or you should provide some ping (i.e. AJAX) mechanism that would reset locktime as long as user is working on locked record
user can save changes to record as long as he/she owns the lock and locktime did not expire
tablename and primarykey are of course samples and you should adjust them to your needs :-)
add a "locked" column to the table, and once a user calls the edit form, set the "locked" db value to the user_id, and after save set it back to false/null.
In your view action, check the locked value
You can either add a field named like locked where you set a status. Maybe you also add a field like lockedtime where you save a timestamp how long the lock is active. That depends on your needs.
There are also possibilities to do this native. Like
SELECT * FROM table WHERE primarykey = x FOR UPDATE;

Alternative to block a table

I have a table called 'messages' (INNODB) where the user can insert their own posts.
I want to put a limitation. In my php script when the table gets to 10 records, you can not add more. The logic of the program is more or less as follows.
Step 1. I run a query to count the lines that are in the table.
Step 2. Recovered that value, I decide whether to insert a new post.
My difficulty is in properly managing the possibility of two user who do the same thing simultaneously. If the user A is in step 1 while user B has just finished entering the tenth post, user A will include the eleventh.
How to avoid it?
You can create CHAR(1) NOT NULL field and cover it with UNIQUE INDEX. This will prevent of inserting more than 10 rows.
Other solution that could work would be to create BEFORE INSERT trigger that checks number of rows and raises error if there are more than 10 (look here for sample) (but in this case you can fail with condition races).
In order to allow you to change your threshold value for the table, you can use a trigger. Because MySQL triggers don't have a "prevent INSERT" option, you need a value in your table set to NOT NULL. The trigger can then set the inserted value for that column to NULL which will prevent the INSERT if your condition check fails.
A trigger like this:
CREATE TRIGGER block_insert
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
DECLARE count INT;
SELECT COUNT(*)
FROM table_name INTO count;
IF count >= 10
THEN
SET NEW.non_nullable_value = NULL;
END IF;
END;
would fail if you inserted an 11th row, like this:
ERROR 1048 (23000): Column 'non_nullable_value' cannot be null
You may wish to set the non-nullable column's name to something that represents its use. You could improve this by having the trigger pull the limit value from a configuration table.
Update
To avoid having to use the non-nullable columns, you could alternatively create an error procedure, and CALL it from your trigger - similar to the example in the "Emulating Check Constraints" section of this page - they're referencing Oracle databases, where a check constraint achieves what you want, but MySQL doesn't support them.
The "error procedure" in the example performs an INSERT of a duplicate row into an error table, causing a unique key error and stops the parent transaction also.
Update 2
As pointed out in the comment below, multiple simultaneous transactions may get round the checks - you'll have to use LOCK TABLES <name> WRITE in order to ensure that they can't.
2/ You can also lock the MySQL table.
execute : LOCK TABLES my_table
Then do your business rules.
execute : UNLOCK TABLES
This also ensure that each action is sequentially executed. (but you have to deal with performance overhead)
Hope this could be useful.
Updated since the comments below : transaction don't work in this case
1/ You are using InnoDB, you can also use database transaction
Open transaction
Then do your business rules.
Commit or rollback your transaction
This will ensure that each action are executed one after another.

Categories