我有一个带有2个字段的表“tbluser”:
我有一个使用此表的多线程/多服务器应用程序。
我想完成以下任务:
我有以下存储过程:
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
应用程序基本上调用存储过程并提供用户名作为参数。 存储过程要么获取用户ID,要么插入用户(如果是新用户)。
我是否正确假设该表已被锁定(只有一个服务器可以插入/查询)?
答案 0 :(得分:4)
我确信以下建议可能会对将来有所帮助。
而不是(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语句(在INSERT期间自动创建事务)是您需要表锁定的唯一时间。 (是的,我在带有和不带TABLOCKX
的两个窗口上对此进行了压力测试,看看它在发布我的消息之前是如何流行的。)
答案 1 :(得分:2)
如果您想保证用户是唯一的,仅方式是唯一约束
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失败......
@userID IS NULL
,因为进程1尚未执行INSERT 这是因为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