如何为此SQL获取正确的锁?

时间:2011-12-01 20:11:41

标签: sql-server data-integrity locks

我的数据库是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)添加到选择中,希望能够获取更新锁并保持另一个。这不起作用。

2 个答案:

答案 0 :(得分:3)

三个选项:

  1. SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
  2. 将锁定提示更改为WITH (UPDLOCK, HOLDLOCK)
  3. 为dbo.aspnet_UsersEvents添加唯一约束,并在插入位置附加TRY/CATCH
  4. 如果省略HOLDLOCK,则可以使用以下脚本确认已执行锁定并立即释放。当使用HOLDLOCK时,您还会看到锁未被释放(没有'释放KEY'输出的锁定引用)。

    Gist script

答案 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 = ...