我的数据库是SQL Server 2005/8。在预订系统中,我们限制24次活动预订。存储过程中的此代码检查: - 当前用户(@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
问题在于它不安全。两个用户可能同时执行测试并得出结论他们都可以预订。两者都插入一行,我们最终会有25个预订。
简单地将其封入交易中是行不通的。我尝试将WITH(UPDLOCK)添加到选择中,希望能够获取更新锁并保持另一个。这不起作用。
答案 0 :(得分:3)
三个选项:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
WITH (UPDLOCK, HOLDLOCK)
TRY/CATCH
。如果省略HOLDLOCK
,则可以使用以下脚本确认已执行锁定并立即释放。当使用HOLDLOCK
时,您还会看到锁未被释放(没有'释放KEY'输出的锁定引用)。
答案 1 :(得分:1)
只需在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
附注:您的SELECT COUNT(*)
重复检查似乎过多,我个人使用NOT EXISTS(SELECT NULL FROM ... WHERE UserID = ..., EventsID = ...
。