用事务为防止并发时多次更新同一记录(锁)

--准备测试表及测试数据
USE tempdb
GO
IF OBJECT_ID('task') IS NOT NULL DROP TABLE task
GO
CREATE TABLE task (
	taskId INT IDENTITY(1,1) PRIMARY KEY
	,taskName NVARCHAR(50) NOT NULL
	,d DATETIME NOT NULL DEFAULT(GETDATE())
	,flag INT NOT NULL DEFAULT(0)
	,mytimestamp TIMESTAMP NOT NULL
)
GO
--插入10000条数据
;WITH cte AS(
	SELECT sv.number FROM [master].dbo.spt_values AS sv WHERE sv.[type]='P' AND sv.number BETWEEN 1 AND 1000
)
INSERT INTO task(taskName)
SELECT '任务'+ltrim(ROW_NUMBER()over(order BY(select 1)))
FROM cte AS a CROSS APPLY (SELECT TOP 10 * FROM cte) AS b

注:下面用 4 种不同的方式来测试, 每种方式测试完之后都要执行上面的代码以便重新开始新的测试。

一、不用事务。

--1. 不用事务的代码
DECLARE @taskId INT
SELECT TOP 1 @taskId=taskId FROM task AS t WITH (XLOCK,ROWLOCK) WHERE flag=0 ORDER BY t.taskId
UPDATE task
SET
	flag = flag+1,
	d = GETDATE()
WHERE taskId=@taskId

验证(不合符要求):

SELECT flag,COUNT(1) AS cnt 
FROM task
GROUP BY flag
/*
flag	cnt
0	9090
3	5
1	827
4	1
2	77
*/

二、用事务(悲观锁):

--2. 悲观锁
BEGIN TRAN
BEGIN TRY
	DECLARE @taskId INT	
	SELECT TOP 1 @taskId=taskId FROM task AS t WITH (XLOCK,ROWLOCK) WHERE flag=0 ORDER BY t.taskId
	UPDATE task
	SET
		flag = flag+1,
		d = GETDATE()
	WHERE taskId=@taskId
	COMMIT TRAN;
END TRY
BEGIN CATCH
	DECLARE @errMsg NVARCHAR(MAX)
	SET @errMsg=ERROR_MESSAGE()
	RAISERROR(16,1,@errMsg)
	ROLLBACK TRAN;
END CATCH

验证 (正确):

SELECT flag,COUNT(1) AS cnt 
FROM task
GROUP BY flag
/*
flag	cnt
0	9000
1	1000
*/

三、乐观锁

--3. 乐观锁(循环的目的是为了可能的情况下必须更新一条记录)
DECLARE @taskId INT,@timestamp TIMESTAMP
WHILE EXISTS(SELECT * FROM task AS t WHERE flag=0)
BEGIN
	SELECT TOP 1 @taskId=taskId,@timestamp=t.mytimestamp 
	FROM task AS t WHERE flag=0 ORDER BY t.taskId
	
	UPDATE task
	SET
		flag = flag+1,
		d = GETDATE()
	WHERE taskId=@taskId AND mytimestamp=@timestamp 
	
	IF @@ROWCOUNT>0
	BEGIN
		BREAK;
	END
END

 

验证:

 

SELECT flag,COUNT(1) AS cnt 
FROM task
GROUP BY flag
/*
flag	cnt
0	9000
1	1000
*/ 

四、用 sp_releaseapplock

1. 先增加备用的存储过程:

IF OBJECT_ID('proc_update') IS NOT NULL DROP PROC proc_update
GO
CREATE PROC proc_update
AS
BEGIN
	SET NOCOUNT ON
	DECLARE @taskId INT
	SELECT TOP 1 @taskId=taskId FROM task AS t WITH (XLOCK,ROWLOCK) WHERE flag=0 ORDER BY t.taskId
	UPDATE task
	SET
		flag = flag+1,
		d = GETDATE()
	WHERE taskId=@taskId
END
GO
IF OBJECT_ID('proc_lock_update') IS NOT NULL DROP PROC proc_lock_update
GO
CREATE PROC proc_lock_update
AS
BEGIN
	DECLARE @result int;
	BEGIN TRY
	BEGIN TRANSACTION;
		EXEC @result = sp_getapplock @Resource = 'proc_update', @LockMode = 'Exclusive';
		IF @result<0
		BEGIN
			RAISERROR ('wait' ,16,1)
			ROLLBACK TRANSACTION
		END
		EXEC proc_update
		EXEC @result =sp_releaseapplock @Resource = 'proc_update';
		COMMIT TRANSACTION;
	END TRY
	BEGIN CATCH
		EXEC @result =sp_releaseapplock @Resource = 'proc_update';
		ROLLBACK TRANSACTION
	END CATCH
END
GO

2. 并发测试代码:

--4. sp_releaseapplock
EXEC proc_lock_update 

验证 (正确):

SELECT flag,COUNT(1) AS cnt 
FROM task
GROUP BY flag
/*
flag	cnt
0	9000
1	1000
*/ 

 

 

序号说明消耗秒数是否正确完成
1无事务,不作特殊处理0.5153N
2用事务,悲观锁0.6704Y
3不用事务,乐观锁3.9225Y
4sp_releaseapplock0.9961Y

 

由上可知:2,3,4 三种方式都可以实现, 具体用哪一种就看你的喜好了。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值