views:

147

answers:

3

I am trying to work out the best way to stop double 'booking' in my application.

I have a table of unique id's each can be sold only once.

My current idea is to use a transaction to check if the chosen products are available, if they are then insert into a 'status' column that it is 'reserved' along with inserting a 'time of update' then if the user goes on to pay I update the status to 'sold'.

Every 10 mins I have a cron job check for 'status' = 'reserved' that was updated more than 10 mins ago and delete such rows.

Is there a better way? I have never used transactions (I have just heard the word banded around) so if someone could explain how I would do this that would be ace.

+3  A: 

What you're attempting to do with your "reserved" status is essentially to emulate transactional behavior. You're much better off letting an expert (mysql) handle it for you.

Have general read about database transactions and then how to use them in MySQL. They aren't too complicated. Feel free to post questions about them here later, and I'll try to respond.

Edit: Now that I think about your requirements... perhaps only using database transactions isn't the best solution - having tons of transactions open and waiting for user action to commit the transactions is probably not a good design choice. Instead, continue what you were doing with "status"="reserved" design, but use transactions in the database to set the value of "status", to ensure that the row isn't "reserved" by two users at the same time.

Mike Atlas
I felt this might be the case, that was why I asked the question (rather than just trying to do it). So can I lock a row so that it can not be read - thus achieving a 'reserved' state.
Mark
If so how long can it be 'locked' for and how can I 'unlock' it if a user just closes the site?
Mark
Transactions can be configured to timeout, and when they timeout, the locked rows will be released and any changes will be rolled back to their previous state. The relevant setting in MySQL is innodb_lock_wait_timeout = 50 (assuming you're using innodb storage engine, which is likely).
Mike Atlas
If you're letting the user "reserve" an item simply by visiting a page on your site, let them know - if you've ever used Ticketmaster's website, they do this to tell you have 10 minutes to complete the purchase or the reserved ticket(s) are released for others to attempt to buy.
Mike Atlas
Mark - check out my edit above.
Mike Atlas
I just realised that there may be an advantage to my 'manual locking' approach... My products are virtual, thus there is no list of available products.. rather a list of bought products (the available ones are the ones that are not taken). As a result if the bought rows were locked they could not be read and thus could not be displayed as 'taken' (bought or reserved).
Mark
Great, we came to the same conclusion. Thank you for your time!
Mark
You're welcome. Thanks for the accepted answer vote.
Mike Atlas
A: 

You do not need to have any added state to do this.

In order to avoid dirty reads, you should set the database to an isolation level of that will avoid them. Namely, REPEATABLE READ or SERIALIZABLE.

You can set the isolation level globally, or session specific. If all your sessions might need the isolation, you may as well set it globally.

Once the isolation level is set, you just need to use a transaction that starts before you SELECT, and optionally UPDATEs the status if the SELECT revealed that it wasn't reserved yet.

Ben S
+2  A: 

despite what others here have suggested, transactions are not the complete solution.

sounds like you have a web application here and selecting and purchasing a reservation takes a couple of pages (steps). this means you would have to hold a transaction open across a couple of pages, which is not possible.

your approach (status column) is correct, however, i would implement it differently. instead of a status column, add two columns: reserved_by and reserved_ts.

  • when reserving a product, set reserved_by to the primary key of the user or the session and reserved_ts to now().
  • when looking for unreserved products, look for ones where reserved_ts is null or more than 10 minutes old. (i would actually look for a couple minutes older than whatever you tell your user to avoid possible race conditions.)
  • a cron job to clear old reservations becomes unnecessary.
longneck
Thank you... That is sensible. I think I will keep status, as I can use it for reserved, bought, disabled... and many other possible status. I already have a date created so I can achieve the same approach. I also have a userid already. Thanks.
Mark