Многопоточная блокировка SQLserver с TABLOCKX - PullRequest
1 голос
/ 15 ноября 2011

У меня есть таблица "tbluser" с 2 полями:

  • userid = integer (автоинкремент)
  • user = nvarchar (100)

У меня есть многопоточное / многосерверное приложение, которое использует эту таблицу.

Я хочу выполнить следующее:

  • Гарантия того, что полевой пользователь уникален в моей таблице
  • Гарантируйте, что комбинация ИД пользователя / пользователя уникальна в памяти каждого сервера

У меня есть следующая хранимая процедура:

CREATE PROCEDURE uniqueuser @user nvarchar(100) AS
BEGIN
BEGIN TRAN
    DECLARE @userID int
    SET  nocount ON
    SET  @userID = (SELECT @userID FROM tbluser WITH (TABLOCKX) WHERE  [user] = @user)
    IF @userID <> ''
    BEGIN
        SELECT  userID = @userID
    END
    ELSE
    BEGIN
        INSERT INTO  tbluser([user]) VALUES (@user)
        SELECT userID = SCOPE_IDENTITY() 
    END 
COMMIT TRAN
END

В основном приложение вызывает хранимую процедуру и предоставляет имя пользователя в качестве параметра,Хранимая процедура либо получает идентификатор пользователя, либо вставляет пользователя, если это новый пользователь.

Правильно ли считать, что таблица заблокирована (только один сервер может вставить / запросить)?

Ответы [ 2 ]

4 голосов
/ 11 августа 2012

Я уверен, что следующий совет может помочь кому-то в будущем.

Вместо (TABLOCKX), попробуйте (TABLOCKX, HOLDLOCK) или даже лучше, , если это единственная процедура, в которой происходит запись в tbluser, вы можете сократить весь набор кодаполностью используя только TABLOCKX и без транзакции через

CREATE PROCEDURE uniqueuser @user nvarchar(100)
AS
--BEGIN TRAN NOT NECESSARY!
    INSERT tbluser WITH (TABLOCKX) ([user])
        SELECT @user
        WHERE NOT EXISTS(SELECT 1 FROM tbluser WHERE [user]=@user)
--COMMIT TRAN NOT NECESSARY!
SELECT userID FROM tbluser WHERE [user]=@user

Таким образом, оператор вставки (который автоматически создает транзакцию на время INSERT) - единственный раз, когда вам нужна блокировка таблицы.(Да, я проверил это на двух окнах как с * 1011, так и без него, чтобы увидеть, как оно сработало, до публикации моего сообщения.)

2 голосов
/ 15 ноября 2011

Если вы хотите гарантировать, что пользователь уникален, способ only состоит в уникальном ограничении

ALTER  TABLE tbluser WITH CHECK
   ADD CONSTRAINT UQ_tbluser_user UQNIUE (user);

Не «бросать свои собственные» уникальные проверки: он потерпит неудачу.

Кэшированные данные в памяти сервера соответствуют тому же ограничению

Я бы сделал это.Сначала ищите пользователя, если не найдена вставка, обработайте уникальную ошибку на всякий случай.И я бы использовал параметр OUTPUT

CREATE PROCEDURE uniqueuser
  @user nvarchar(100)
  -- ,@userid int = NULL OUTPUT
AS
SET NOCOUNT, XACT_ABORT ON;
DECLARE @userID int;

BEGIN TRY
    SELECT @userID FROM tbluser WHERE [user] = @user;
    IF @userID IS NULL
    BEGIN
        INSERT INTO  tbluser([user]) VALUES (@user);
        SELECT userID = SCOPE_IDENTITY() ;
    END
END TRY
BEGIN CATCH
    -- test for a concurrent call that just inserted before we did
    IF ERROR_NUMBER() = 2627
        SELECT @userID FROM tbluser WHERE [user] = @user;
    ELSE
       -- do some error handling
END CATCH
-- I prefer output parameter for this SELECT @userID AS UserID
GO

Редактировать: почему TABLOCKX терпит неудачу ...

  • Вы блокируете таблицу только на время SELECT.
  • 2-й процесс, запущенный одновременно, начнет чтение таблицы после снятия блокировки процессом 1
  • Оба процесса могут иметь @userID IS NULL, поскольку процесс 1 еще не выполнил INSERT
  • Процесс 2 получает ошибку, когда он ВСТАВЛЯЕТ

Это происходит потому, что TABLOCKX изменяет изоляцию блокировки и детализацию, а не продолжительность.

Редактировать 2: для SQL Server 2000

CREATE PROCEDURE uniqueuser
   @user nvarchar(100)
   -- ,@userid int = NULL OUTPUT
AS
SET NOCOUNT, XACT_ABORT ON;
DECLARE @userID int;

SELECT @userID FROM tbluser WHERE [user] = @user;
IF @userID IS NULL
BEGIN
    INSERT INTO  tbluser([user]) VALUES (@user);
    IF @@ERROR = 2627
        SELECT @userID FROM tbluser WHERE [user] = @user;
    ELSE
        RAISERROR ('the fan needs cleaning', 16, 1);   
    SELECT userID = SCOPE_IDENTITY();
END
GO
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...