0

I have the following algorithm:

  1. Choose a row that has no answer and that is not locked (status 0)
  2. Lock this row to prevent others from getting it (status 1)

there is also status 2 - row has an answer and finished.

SELECT * FROM details WHERE RowStatus=0 AND taskid=".$task_id." ORDER BY RAND() LIMIT 1

 UPDATE details SET RowStatus=1 ,Agent='".$_SESSION['username']."' where TaskID=".$row['TaskID']." and RowId=".$row['RowId']

The problem is that if i have 10 rows left and 8 agents working on the task, sometimes happens that they submit previous row in exactly the same time. Then both of them select the same row and only one of them locks the row with his name meaning - one of them is working on the same row.

Its sounds like a minor issue but somehow i got this happen 5-10 times in a 2500 rows task. Is there any way to lock the row in the same time as selecting it? Prevent other user to select this row?

Strawberry
  • 33,750
  • 13
  • 40
  • 57
Alex
  • 169
  • 1
  • 1
  • 9
  • Is there any real reason to order by rand() or is it just to try and avoid the conflicts? – Jakub Kania Feb 20 '14 at 10:25
  • you can use transactions if you are working on `InnoDB` – Kelu Thatsall Feb 20 '14 at 10:30
  • At first i didnt use random but then the chance that they both will get the same row increases. – Alex Feb 20 '14 at 10:31
  • Unfortunately not InnoDB :( – Alex Feb 20 '14 at 10:32
  • 1
    If you are not working on InnoDB, but on MyIsam, then the only option is to lock the whole table. – krokodilko Feb 20 '14 at 10:37
  • Well do you have any specific reason to use myisam? Because there are not that many differences in simple usage, but it allows you to create those locks. – Kelu Thatsall Feb 20 '14 at 10:39
  • @kordirko, cant do that because if all the table is locked then other users will get "end task" page because there are no free rows to work on – Alex Feb 20 '14 at 10:39
  • @ Kelu Thatsall, any reason it all. Its just a part of a big project and i dont have an influence to change it – Alex Feb 20 '14 at 10:40
  • You can create a PROCEDURE (mysql side) to select the row and lock it afterwards. This should allow you to be safe if you use this procedure for selecting, never accessing it directly. – Kelu Thatsall Feb 20 '14 at 10:44
  • If i use a procedure then it cant be run In parallel for couple users? – Alex Feb 20 '14 at 10:45
  • It can be used by all of the users but I'm not sure if it assures that you are safe from the issue you have. I don't know that myself and I'm trying to find some info on that topic... ;) – Kelu Thatsall Feb 20 '14 at 10:52
  • @Kelu Thatsall, the basic problem is that MyIsam engine is used, not InnoDb, so individual rows cannot be locked regardless of using a procedure or not. – krokodilko Feb 20 '14 at 11:07
  • Look my answer, I think that can be done on MyIsam + PHP and it will work similar to locking. – Kelu Thatsall Feb 20 '14 at 11:28

4 Answers4

0

you should lock row (of course if you are using InnoDB). Scenario is below:

begin;
select ... FOR UPDATE;

doing what you want with task

update selected task

commit;
ziollek
  • 1,973
  • 1
  • 12
  • 10
0

One way (much better) is using a transaction: PHP + MySQL transactions examples

The second options is making an UPDATE first (lock task), and then SELECT task by Agent and Status.

Community
  • 1
  • 1
lukasz
  • 1
  • 3
0

A simple way may be:

After UPDATE, run a SELECT on that row and check that Agent is the current user. If it isn't, re-select a new row

neelsg
  • 4,802
  • 5
  • 34
  • 58
0

I got an idea how to do it without any special access, stored procedures, triggers or transactions.

You need to create a table called for example Semaphores with two columns - name (text) and state (int). And for your purpose we will create one row called details with the state of 0.

Then your code can look like this:

$semaphore = query("UPDATE Semaphore SET state = 1 WHERE name = 'details'"); // It's just pseudo code for querying database - do it yourself

while(affected_rows($semaphore) == 0) {
    $semaphore = query("UPDATE Semaphore SET state = 1 WHERE name = 'details'");
    sleep(10); // sleep to not query too often
}

fetch_and_query("SELECT * FROM details WHERE RowStatus=0 AND taskid=".$task_id." ORDER BY RAND() LIMIT 1");

query("UPDATE details SET RowStatus=1 ,Agent='".$_SESSION['username']."' where TaskID=".$row['TaskID']." and RowId=".$row['RowId']);

query("UPDATE Semaphore SET state = 0");

This way every user who runs this query will have to wait for others to finish their SELECT+UPDATE thing. Also it is just a trick - the work of the lock does the part with updating the Semaphore table, you just need one query to find out if the value of the lock was 0. If it was then the function affected_rows will tell you that you can go on with your script (please note that affected_rows might be in PHP for example mysqli_affected_rows but also any other equivalent). Remember that when you update a row which doesn't need updating then this function will return a value of zero anyway.

My answer on my blog: http://kelostrada.pl/en/mysql-en/locking-rows-with-myisam-php/

Kelu Thatsall
  • 2,494
  • 1
  • 22
  • 50