2011-12-01 24 views
3

我的数据库是SQL Server 2005/8。在预订系统中,我们有24项预定的活动限制。存储过程中的此代码将检查: - 当前用户(@UserId)尚未在事件(@EventsID) 上预订 - 当前事件的当前预订列表为24 - 插入新的预订。如何获得此SQL的正确锁定?

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)到选择中,希望能够更新锁并保持其他锁。这是行不通的。

回答

3

三个选项:

  1. SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
  2. 更改锁提示WITH (UPDLOCK, HOLDLOCK)
  3. 添加唯一约束来dbo.aspnet_UsersEvents和刀片周围TRY/CATCH

如果您省略了HOLDLOCK,则可以使用以下脚本来确认锁已被采用并立即释放。当使用HOLDLOCK时,您还将看到锁未释放(在'KEY'输出上'释放锁定参考')。

Gist script

+0

为什么这么复杂?看到我的答案。 –

+0

因为你的答案不是事务安全的。单一的陈述总是一致的是一个常见的谬误。 –

+1

请您指出一些SQL Server不能保证单个语句不一致的文档。我假设他没有以'READ UNCOMMITTED'运行。 –

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

+0

你提出一个有用的观点,Exists测试比count更清晰。谢谢。 – RichardHowells