sql server对并发的处理-乐观锁和悲观锁
假如两个线程同时修改数据库同一条记录,就会导致后一条记录覆盖前一条,从而引发一些问题。
例如:
一个售票系统有一个余票数,客户端每调用一次出票方法,余票数就减一。
情景:
总共300张票,假设两个售票点,恰好在同一时间出票,它们做的操作都是先查询余票数,然后减一。
一般的sql语句:
1
2
3
4
5
6
7
8
9
|
declare
@
count
as
int
begin
tran
select
@
count
=
count
from
ttt
WAITFOR DELAY
'00:00:05'
--模拟并发,故意延迟5秒
update
ttt
set
count
=@
count
-1
commit
TRAN
SELECT
*
FROM
ttt
|
问题就在于,同一时间获取的余票都为300,每个售票点都做了一次更新为299的操作,导致余票少了1,而实际出了两张票。
打开两个查询窗口,分别快速运行以上代码即可看到效果。
定义解释:
悲观锁:相信并发是绝大部分的,并且每一个线程都必须要达到目的的。
乐观锁:相信并发是极少数的,假设运气不好遇到了,就放弃并返回信息告诉它再次尝试。因为它是极少数发生的。
悲观锁解决方案:
1
2
3
4
5
6
7
|
declare
@
count
as
int
begin
tran
select
@
count
=
count
from
tb
WITH
(UPDLOCK)
WAITFOR DELAY
'00:00:05'
--模拟并发,故意延迟5秒
update
tb
set
count
=@
count
-1
commit
tran
|
在查询的时候加了一个更新锁,保证自查询起直到事务结束不会被其他事务读取修改,避免产生脏数据。
从而可以解决上述问题。
乐观锁解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
--首先给表加一列timestamp
ALTER
TABLE
ttt
ADD
timesFlag
TIMESTAMP
NOT
null
然后更新时判断这个值是否被修改
declare
@
count
as
int
DECLARE
@flag
AS
TIMESTAMP
DECLARE
@rowCount
AS
int
begin
tran
select
@
count
=
COUNT
,@flag=timesflag
from
ttt
WAITFOR DELAY
'00:00:05'
update
ttt
set
count
=@
count
-1
WHERE
timesflag=@flag
--这里加了条件
SET
@rowcount=@@ROWCOUNT
--获取被修改的行数
commit
TRAN
--对行数进行判断即可
IF @rowCount=1
PRINT
'更新成功'
ELSE
PRINT
'更新失败'
|
这便是乐观锁的解决方案,可以解决并发带来的数据错误问题,但不保证每一次调用更新都成功,可能会返回'更新失败'
悲观锁和乐观锁
悲观锁一定成功,但在并发量特别大的时候会造成很长堵塞甚至超时,仅适合小并发的情况。
乐观锁不一定每次都修改成功,但能充分利用系统的并发处理机制,在大并发量的时候效率要高很多。
评论
#1楼
#2楼
这话有点问题吧
假如这三个用户同时到达update这里,这个时候update更新语句会把并发串行化,后者覆盖前者。 所以并不会出现你说的这种情况。
但会出现另外一种问题就是 多个窗口打开都显示 10张余票, 其中8张卖了,但是 仍然显示10张, 此时购买超过2张 就会提示 余票不足。
#3楼[楼主]
这取决于你的新值是在程序里给的还是通过update set val=val-1