views:

948

answers:

4

I want to implement SO like tags on userpost. I have a table called tag_data with columns tagId, title, count. I have a separate table that links the relationship between a post and the many tags it may use.

Heres the problem, how do i get the current count, increase or decrease it by one and store it SAFELY. So no other connection/thread will update it between the time i do select and update?

A: 

Use transactions.

jrista
i think the poster wanted a bit more detail...;)
Mitch Wheat
...use transactions *well*.
Michael Petrotta
If no one says anything else i'll accept this. It sounds like the solution. I was thinking about transactions but i was worried that it might be an all or nothing on that specific connection.
acidzombie24
lol, sorry for the short answer...I was running out the door at the time.
jrista
Its good tho. lol
acidzombie24
+3  A: 

I assume you also want the new count, other wise this is a no brainer, just update set count=count+1.

If your db support output clause on UPDATE (eg. SQL Server 2K5 or 2K8):

UPDATE table
   SET count = count + 1
   OUTPUT inserted.count
   WHERE id=@id;

otherwise:

begin transaction
update table 
    set counter=counter+1
    where id=@id;
select counter
    from table
    where id=@id;
commit;
Remus Rusanu
Good answer. I am confused about two things. It looks like update/select is one statement so why do you need a transaction? The second is judging by the other 2 response it seems like transaction will lock the database until it is finished, so why is MikeyB answer bad? I read your reply but what happens?, others threads can read but not write during the transaction?
acidzombie24
The X lock aquired by the update is help *for the duration of the transaction* and prevents anyone else from reading, or updating, the counter until you commit. This gives you a save spot to do your select knowing that you'll retrieve the actual value you updated.In Mikey's case the S lock acquired by the select is *not* help after the statement completes (in normal conditions), thus allowing a second thread to sneak in and update the value by the time your update is executed, and you'll overwrite that update. Hence the 'lost update'.
Remus Rusanu
A: 

Pseudocode:

begin transaction
A = select count from tag_data where tagId = TagId
update tag_data set count = A+1 where tagId = TagId
commit
end transaction

I highly recommend making a stored procedure called, say, increment_tag(TagId) that does the above :)

MikeyB
Nothing prevents a concurrent thread from reading the same count, incrementing it and updating it. This is the canonical example of the lost update case in transaction processing. You'd need to protect the initial select with at least a repeatable-read isolation level (or a holdlock).
Remus Rusanu
this (very bad) solution can be also fixed by changing the SELECT statement into SELECT ... FOR UPDATE. This will result in obtaining lock on this row. This lock will hold other transactions that want to do update/delete or another select for update on that row, until this transaction commits/rollbacks. Remember that you need to ensure that every piece of code that modifies this counter also uses select for update or uses "update first then select" rule, otherwise all this is useless.
Łukasz Korzybski
A: 

SET Count = Count + 1 is IMHO the easiest solution..

More generally the concept of being able to get data, process it and while its being processed demand there be no underlying changes before writing the results of the processing is ususally not a reasonable one to have if you also require a scalable system.

You can of course do this and in many environments get away with it.. however these approaches will put severe limits on the scalaibility and complexity of an application before concurrency issues render the system unusable.

IMHO the better approach is to take an optimistic route and detect/retry if in the unususal case something you care about did change.

SELECT Count AS old ... FROM ...

.. processing ...

UPDATE ... SET Count = oldplus1 WHERE Count = old AND ...

Unless UPDATE gives you the rowcount your expecting you assume the data was modified and try again until it succeeds.

Einstein