zoukankan      html  css  js  c++  java
  • sql server对并发的处理-乐观锁和悲观锁(转)

    假如两个线程同时修改数据库同一条记录,就会导致后一条记录覆盖前一条,从而引发一些问题。

    例如:

      一个售票系统有一个余票数,客户端每调用一次出票方法,余票数就减一。

    情景: 

      总共300张票,假设两个售票点,恰好在同一时间出票,它们做的操作都是先查询余票数,然后减一。

    一般的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,而实际出了两张票。

      打开两个查询窗口,分别快速运行以上代码即可看到效果。

    定义解释:

      悲观锁:相信并发是绝大部分的,并且每一个线程都必须要达到目的的。

      乐观锁:相信并发是极少数的,假设运气不好遇到了,就放弃并返回信息告诉它再次尝试。因为它是极少数发生的。

    悲观锁解决方案:

    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 '更新失败'

    这便是乐观锁的解决方案,可以解决并发带来的数据错误问题,但不保证每一次调用更新都成功,可能会返回'更新失败'

    悲观锁和乐观锁

      悲观锁一定成功,但在并发量特别大的时候会造成很长堵塞甚至超时,仅适合小并发的情况。

      乐观锁不一定每次都修改成功,但能充分利用系统的并发处理机制,在大并发量的时候效率要高很多。

    -------------------

    但是,查看msdn上的资料,官方说不建议自己加锁:

    SQL Server 数据库引擎查询优化器几乎总是会选择正确的锁级别。 建议只在必要时才使用表级锁提示来更改默认的锁行为。 禁止锁级别反过来会影响并发。

    对于上面的问题,其实是自己写的sql语句有些不恰当,为什么要把count先查出来呢,直接进行操作就行了啊,不用加upd锁:

    begin tran
        -- select @count=count from tb WITH(UPDLOCK)
      WAITFOR DELAY '00:00:05' --模拟并发,故意延迟5秒
        update tb set count=count-1
    commit tran
  • 相关阅读:
    kernel list 实践
    rpm打包
    void out2() const{
    剑指offer python版 两个链表的第一个公共结点
    剑指offer python版 数组中的逆序对
    剑指offer python版 字符串中第一个只出现一次的字符
    剑指offer python版 丑数 (只含有2,3,5因子)
    剑指offer python版 最长不含重复字符的子字符
    剑指offer python版 礼物的最大价值
    剑指offer python版 数字序列中某一位的数字
  • 原文地址:https://www.cnblogs.com/25miao/p/10675762.html
Copyright © 2011-2022 走看看