MYSQL面试知识点四、锁

从数据库系统角度分为三种:排他锁(写锁)、共享锁(读锁)、更新锁。
从程序员角度分为两种:一种是悲观锁,一种乐观锁。

1. 悲观锁(Pessimistic Lock)

  顾名思义,很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人拿这个数据就会block(阻塞),直到它拿锁。

传统的关系数据库里用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。

1.1. 按作用范围划分(行锁、表锁)

  1. 行锁:作用范围是行级别。
  2. 作用范围是整张表。

  数据库能够确定那些行需要锁的情况下使用行锁,如果不知道会影响哪些行的时候就会使用表锁。

  举个例子,一个用户表user,有主键id和用户生日birthday。
  当你使用update … where id=?这样的语句时,数据库明确知道会影响哪一行,它就会使用行锁;
  当你使用update … where birthday=?这样的的语句时,因为事先不知道会影响哪些行就可能会使用表锁。

1.2. 按使用性质划分(共享锁、排它锁、更新锁)

  1. 共享锁(Share Lock):也叫读锁,用于所有的只读数据操作。共享锁是非独占的,允许多个并发事务读取其锁定的资源

性质

  1. 多个事务可封锁同一个共享页;
  2. 任何事务都不能修改该页;
  3. 通常是该页被读取完毕,S锁立即被释放。
  1. 排他锁(Exclusive Lock):也叫写锁,表示对数据进行写操作。如果一个事务对对象加了排他锁,其他事务就不能再给它加任何锁了。

性质

  1. 仅允许一个事务封锁此页;
  2. 其他任何事务必须等到排他锁被释放才能对该页进行访问;
  3. 排他锁一直到事务结束才能被释放。
  1. 更新锁(Update Lock):在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。

性质

  1. 用来预定要对此页施加排他锁,它允许其他事务读,但不允许再施加U锁或X锁;
  2. 当被读取的页要被更新时,则升级为排他锁;
  3. 更新锁一直到事务结束时才能被释放。

当使用共享锁时,修改数据的操作分为两步:

  • 首先获得一个共享锁,读取数据,
  • 然后将共享锁升级为排他锁,再执行修改操作。
  • 这样如果有两个或多个事务同时对一个事务申请了共享锁,在修改数据时,这些事务都要将共享锁升级为排他锁。这时,这些事务都不会释放共享锁,而是一直等待对方释放,这样就造成了死锁。
  • 如果一个数据在修改前直接申请更新锁,在数据修改时再升级为排他锁,就可以避免死锁。

2. 乐观锁(Optimistic Lock)

  顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以,不会上锁。但是在更新的时候会判断一下在此期间别人有没有更新这个数据,可以使用版本号等机制。

2.1. 乐观锁的实现方式

  • 版本号(version)

版本号(记为version):就是给数据增加一个版本标识,在数据库上就是表中增加一个version字段,每次更新把这个字段加1,读取数据的时候把version读出来,更新的时候比较version,如果还是开始读取的version就可以更新了,如果现在的version比老的version大,说明有其他事务更新了该数据,并增加了版本号,这时候得到一个无法更新的通知,用户自行根据这个通知来决定怎么处理,比如重新开始一遍。

  • 时间戳(使用数据库服务器的时间戳)

    时间戳(timestamp):和版本号基本一样,只是通过时间戳来判断而已,注意时间戳要使用数据库服务器的时间戳不能是业务系统的时间。

  • 待更新字段

    待更新字段:和版本号方式相似,只是不增加额外字段,直接使用有效数据字段做版本控制信息,因为有时候我们可能无法改变旧系统的数据库表结构。假设有个待更新字段叫count,先去读取这个count,更新的时候去比较数据库中count的值是不是我期望的值(即开始读的值),如果是就把我修改的count的值更新到该字段,否则更新失败。java的基本类型的原子类型对象如AtomicInteger就是这种思想。

  • 所有字段

    所有字段:和待更新字段类似,只是使用所有字段做版本控制信息,只有所有字段都没变化才会执行更新。

2.2. 乐观锁几种方式的区别

  新系统设计可以使用version方式和timestamp方式,需要增加字段,应用范围是整条数据,不论那个字段修改都会更新version,也就是说两个事务更新同一条记录的两个不相关字段也是互斥的,不能同步进行。旧系统不能修改数据库表结构的时候使用数据字段作为版本控制信息,不需要新增字段,待更新字段方式只要其他事务修改的字段和当前事务修改的字段没有重叠就可以同步进行,并发性更高。

3. 并发控制会造成两种锁

  并发控制会造成活锁和死锁,就像操作系统那样,会因为互相等待而导致。

3.1. 活锁

定义:指的是T1封锁了数据R,T2同时也请求封锁数据R,T3也请求封锁数据R,当T1释放了锁之后,T3会锁住R,T4也请求封锁R,则T2就会一直等待下去。
解决方法:采用“先来先服务”策略可以避免。

3.2. 死锁

定义:就是我等你,你又等我,双方就会一直等待下去。比如:T1封锁了数据R1,正请求对R2封锁,而T2封住了R2,正请求封锁R1,这样就会导致死锁,死锁这种没有完全解决的方法,只能尽量预防。

产生死锁的原因:

  1. 竞争资源,

系统中的资源可以分为两类:

  1. 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
  2. 不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
  1. 进程间推进顺序非法
  1. 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
  2. 例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

产生死锁的必要条件:

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

解决死锁的基本方法:

  1. 预防死锁
  1. 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  2. 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  3. 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  4. 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
  1. 避免死锁
  1. 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
  2. 银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
  1. 检测死锁
  1. 首先为每个进程和每个资源指定一个唯一的号码;
  2. 然后建立资源分配表和进程等待表。
  1. 解除死锁
  1. 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
  2. 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值