简体   繁体   English

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

[英]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: 三种选择:

  1. SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
  2. Change the lock hint to WITH (UPDLOCK, HOLDLOCK) 将锁定提示更改为WITH (UPDLOCK, HOLDLOCK)
  3. Add a unique constraint to dbo.aspnet_UsersEvents and a TRY/CATCH around the insert. 为dbo.aspnet_UsersEvents添加一个唯一约束,并在插入周围添加一个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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM