3

我的数据库是 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) 添加到选择中,希望其中一个会获取更新锁并将另一个锁在外面。那是行不通的。

4

2 回答 2

3

三个选项:

  1. SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
  2. 将锁定提示更改为WITH (UPDLOCK, HOLDLOCK)
  3. 向 dbo.aspnet_UsersEvents 添加一个唯一约束并TRY/CATCH在插入周围添加一个。

您可以使用以下脚本来确认锁定已被占用并在您省略 时立即释放HOLDLOCK。您还将看到使用时未释放锁(在 KEY 输出上没有“释放锁引用”)HOLDLOCK

要点脚本

于 2011-12-01T21:22:31.280 回答
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 = ....

于 2011-12-01T21:25:14.327 回答