[英]How do I get the right locks for this SQL?
My database is SQL Server 2005/8. 我的数据库是SQL Server 2005/8。 In a booking system we have a limit of 24 bookings on an event.
在预订系统中,我们限制24次活动预订。 This code in a stored procedure checks: - that the current user (@UserId) is not already booked on the event (@EventsID) - that the current event has a current booking list of under 24 - inserts a new booking.
存储过程中的此代码检查: - 当前用户(@UserId)尚未在事件上预订(@EventsID) - 当前事件具有24以下的当前预订列表 - 插入新预订。
BEGIN TRANSACTION
IF (((select count (*) from dbo.aspnet_UsersEvents with (updlock)
where UserId = @UserId and EventsId = @EventsId) = 0)
AND ((SELECT Count(*) FROM dbo.aspnet_UsersEvents with (updlock)
WHERE EventsId = @EventsId) < 24))
BEGIN
insert into dbo.aspnet_UsersEvents (UserId, EventsId)
Values (@UserId, @EventsId)
END
COMMIT
The problem is that it is not safe. 问题是它不安全。 Two users might perform the test simultaneously and conclude they can both book.
两个用户可能同时执行测试并得出结论他们都可以预订。 Both insert a row and we end up with 25 bookings.
两者都插入一行,我们最终有25个预订。
Simply enclosing it in a transaction does not work. 简单地将其封闭在交易中是行不通的。 I tried adding WITH (UPDLOCK) to the selects in the hope that one would take update locks and keep the other out.
我尝试将WITH(UPDLOCK)添加到选择中,希望能够获取更新锁并保持另一个。 That does not work.
这不起作用。
Three options: 三种选择:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
WITH (UPDLOCK, HOLDLOCK)
WITH (UPDLOCK, HOLDLOCK)
TRY/CATCH
around the insert. TRY/CATCH
。 You can use the following script to confirm that the lock is taken and immediately released if you omit HOLDLOCK
. 如果省略
HOLDLOCK
,则可以使用以下脚本确认已执行锁定并立即释放。 You will also see that the lock is not released (no 'releasing lock reference on KEY' output) when HOLDLOCK
is used. 当使用
HOLDLOCK
时,您还将看到锁定未释放(没有'释放KEY'输出上的锁定引用)。
( Gist script ) ( Gist脚本 )
Just do it in one statement, at READ COMMITTED
or higher. 只需在
READ COMMITTED
或更高版本的一个语句中执行此操作即可。
INSERT dbo.aspnet_UsersEvents
(UserId,EventsId)
OUTPUT inserted.UserEventsId -- Or whatever, just getting back one row identifies the insert was successful
SELECT @UserId
, @EventsId
WHERE ( SELECT COUNT (*)
FROM dbo.aspnet_UsersEvents
WHERE UserId = @UserId
AND EventsId = @EventsId ) = 0
AND ( SELECT COUNT(*)
FROM dbo.aspnet_UsersEvents
WHERE EventsId = @EventsId ) < 24
Side note: your SELECT COUNT(*)
for duplicate checking seems excessive, personally I'd use NOT EXISTS(SELECT NULL FROM ... WHERE UserID = ..., EventsID = ...
. 旁注:重复检查的
SELECT COUNT(*)
似乎过多,我个人使用NOT EXISTS(SELECT NULL FROM ... WHERE UserID = ..., EventsID = ...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.