Как получить правильные блокировки для этого SQL? - PullRequest
3 голосов
/ 02 декабря 2011

Моя база данных - 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 ]

3 голосов
/ 02 декабря 2011

Три варианта:

  1. SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
  2. Изменить подсказку блокировки на WITH (UPDLOCK, HOLDLOCK)
  3. Добавьте уникальное ограничение для dbo.aspnet_UsersEvents и TRY/CATCH вокруг вставки.

Вы можете использовать следующий скрипт, чтобы подтвердить, что блокировка снята и немедленно снята, если вы опустите HOLDLOCK. Вы также увидите, что блокировка не снимается (нет «освобождения ссылки блокировки на выходе KEY»), когда используется HOLDLOCK.

( Сценарий )

1 голос
/ 02 декабря 2011

Просто сделайте это в одном утверждении, на 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 = ....

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...