假如两个线程同时修改数据库同一条记录,就会致使后一条记录覆盖前一条,从而引起一些问题。sql
例如:数据库
一个售票系统有一个余票数,客户端每调用一次出票方法,余票数就减一。并发
情景: 线程
总共300张票,假设两个售票点,刚好在同一时间出票,它们作的操做都是先查询余票数,而后减一。blog
通常的sql语句:事务
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,而实际出了两张票。it
打开两个查询窗口,分别快速运行以上代码便可看到效果。class
定义解释:效率
悲观锁:相信并发是绝大部分的,而且每个线程都必需要达到目的的。date
乐观锁:相信并发是极少数的,假设运气很差遇到了,就放弃并返回信息告诉它再次尝试。由于它是极少数发生的。
悲观锁解决方案:
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
在查询的时候加了一个更新锁,保证自查询起直到事务结束不会被其余事务读取修改,避免产生脏数据。
从而能够解决上述问题。
乐观锁解决方案:
--首先给表加一列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 '更新失败'
这即是乐观锁的解决方案,能够解决并发带来的数据错误问题,但不保证每一次调用更新都成功,可能会返回'更新失败'
悲观锁和乐观锁
悲观锁必定成功,但在并发量特别大的时候会形成很长堵塞甚至超时,仅适合小并发的状况。
乐观锁不必定每次都修改为功,但能充分利用系统的并发处理机制,在大并发量的时候效率要高不少。