Если вы хотите гарантировать, что пользователь уникален, способ 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