views:

70

answers:

3

Hi there,

I have a table of chalets where a chalet is referenced by an account...

CHALET
------
int ChaletId PK
int Berth

ACCOUNT
-------
int AccountId PK
int ChaletId FK

The chalets start off un-referenced. When a user buys a chalet the code needs to find an unreferenced chalet and assign it to a newly created account. I think that the returned chalet needs to have an UPDLOCK on it until the account that will reference it has been commited in order to stop a concurrent shopper from being assigned the same chalet.

How can I write the SELECT that fetches a chalet? I was thinking something like this..

SELECT * FROM CHALET WITH (UPDLOCK) c
LEFT JOIN ACCOUNT a
ON c.ChaletId = a.ChaletID
WHERE a.ChaletID is null
AND Berth = 4

I think the problem is that if this query is being run concurrently then one query might lock half of one table and another might lock the other half and a dead lock would ensure. Is there a way around this? For example, is it possible to lock the selected chalet rows in the same orders?

Cheers, Ian.

A: 

try

SELECT * FROM CHALET WITH (UPDLOCK, HOLDLOCK) c
LEFT JOIN ACCOUNT a
ON c.ChaletId = a.ChaletID
WHERE a.ChaletID is null
AND Berth = 4

But why oh why are you not using an identity property for this kind of stuff instead of trying to roll your own?

SQLMenace
Hi there, please could you explain what you mean by using an identity property? If you mean an identity column which automatically generates a new id when a row is inserted then I don't understand its relevance to this problem.
Ian Warburton
....the assignment order of the chalets depends on the desired chalet berth, the class of the chalet and often chalets are put back on sale. So the id of the 'next available chalet' isn't just a matter of what's the next integer after the id of the last one assigned.
Ian Warburton
Also, I don't understand why you've added HOLDLOCK. This apparently holds shared locks for the duration of a transaction. But if every lock is an update lock then there won't be any shared locked to hold.
Ian Warburton
+1  A: 

Would (UPDLOCK, ROWLOCK, READPAST) do what you need?

Martin Smith
Hi Martin, that looks good. But why did you add ROWLOCK? Its not mentioned in the tip that you linked to.
Ian Warburton
Is it because READPAST, 'will read only past row-level locks'? http://www.mssqlcity.com/Articles/Adm/SQL70Locks.htm#part_4 If so then I'm surprised its not mentioned in the tip.
Ian Warburton
@Ian - Yes. If it takes a page lock then the other transaction will get blocked waiting for the page lock to be released.
Martin Smith
+1  A: 

I think you are trying to use SQL Concurrency locking when you really need your application to handle provisional reservations.

  • Create a flag column or separate table of the reservations that are in-flight.
  • Make all your other queries exclude items that are in the process of being reserved.
  • In the case of a rollback, you would need to unwind that reservation.
Rawheiser
Hi Rawheiser, the query I'm working on here is locking the chalet rows until the corresponding provisional reservations have been created. Its to stop *multiple* provisional reservations for the same chalet.
Ian Warburton
I suppose I could achieve this with a unique constraint on the table of reservations. Except then one would have to worry about implementing retries to fetch fresh chalets when the constraint is violated.
Ian Warburton
You would need to separate the provisional reservation transaction from the completion transaction. Then there would be no locking issues.All your queries would have to exclude that provisional reservation, They wouldn't be visible, and the 2nd request would not occur. I would recommend building that into a view to centralize the exclusion logic.I would also put an time on the provisional reservation, so that if someone doesn't complete the reservation in say 5 minutes, that provisional reservation expires.
Rawheiser
Two concurrent users request a chalet to reserve. Let's say there's one chalet that's not reserved and both users retrieve it. One user's transaction creates a reservation row that references it. Now what does the second do? They're going to try and reserve a reserved chalet because they retrieved it before it was reserved and so before it was hidden! The locking issue is on the provisional reservation transaction. The completion transaction *is* separate - I'm not holding the update lock until the user's submitted their order.
Ian Warburton
Just wanted to clarify something... my original question was referring to retrieving a chalet when a user completes their payment. I've started talking in terms of 'reservations' because the app that this is from is now retrieving the chalets when a user adds a chalet to their cart. However, its the same principle. In both cases one is retrieving a chalet to be exclusively referenced by a row in another table.
Ian Warburton