数据库锁分类和总结

数据库锁分类和总结锁锁是网络数据库中的一个非常重要的概念 当多个用户同时对数据库并发操作时 会带来数据不一致的问题 所以 锁主要用于多用户环境下保证数据库完整性和一致性 帮助理解 以商场的试衣间为例 每个试衣间都可供多个消费者使用 因此 可能出现多个消费者同时需要使用试衣间试衣服 为了避免冲突 试衣间装了锁 某一个试衣服的人在试衣间里把锁锁住了 其他顾客就不能从外面打开了 只能等待里面的顾客试完衣服

是网络数据库中的一个非常重要的概念,当多个用户同时对数据库并发操作时,会带来数据不一致的问题,所以,锁主要用于多用户环境下保证数据库完整性和一致性。

帮助理解:以商场的试衣间为例,每个试衣间都可供多个消费者使用,因此,可能出现多个消费者同时需要使用试衣间试衣服。为了避免冲突,试衣间装了锁,某一个试衣服的人在试衣间里把锁锁住了,其他顾客就不能从外面打开了,只能等待里面的顾客试完衣服,从里面把锁打开,外面的人才能进去。

数据库锁出现的目的:处理并发问题

并发控制的主要采用的技术手段:乐观锁、悲观锁和时间戳。

锁分类

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

悲观锁(Pessimistic Lock)

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

悲观锁(Pessimistic Lock):正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

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

悲观锁按使用性质划分
共享锁(Share Lock)
排他锁(Exclusive Lock)

产生排他锁的SQL语句如下:select * from ad_plan for update;

更新锁

U锁,在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。

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


悲观锁按作用范围划分为:行锁、表锁。(这部分摘自https://www.jianshu.com/p/eb41df )
行锁

锁的作用范围是行级别。

表锁

锁的作用范围是整张表。

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

乐观锁(Optimistic Lock)

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

乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

乐观锁的实现方式(这部分摘自https://www.jianshu.com/p/eb41df )
  • 版本号(version)

版本号(记为version):就是给数据增加一个版本标识,在数据库上就是表中增加一个version字段,每次更新把这个字段加1,读取数据的时候把version读出来,更新的时候比较version,如果还是开始读取的version就可以更新了,如果现在的version比老的version大,说明有其他事务更新了该数据,并增加了版本号,这时候得到一个无法更新的通知,用户自行根据这个通知来决定怎么处理,比如重新开始一遍。这里的关键是判断version和更新两个动作需要作为一个原子单元执行,否则在你判断可以更新以后正式更新之前有别的事务修改了version,这个时候你再去更新就可能会覆盖前一个事务做的更新,造成第二类丢失更新,所以你可以使用update … where … and version=”old version”这样的语句,根据返回结果是0还是非0来得到通知,如果是0说明更新没有成功,因为version被改了,如果返回非0说明更新成功。

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

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

  • 待更新字段

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

  • 所有字段

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


乐观锁几种方式的区别(这部分摘自https://www.jianshu.com/p/eb41df )

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

并发控制会造成两种锁(这部分摘自https://www.cnblogs.com/ismallboy/p/5574006.html)

  • 活锁
  • 死锁

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

活锁

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

死锁

定义:就是我等你,你又等我,双方就会一直等待下去。比如:T1封锁了数据R1,正请求对R2封锁,而T2封住了R2,正请求封锁R1,这样就会导致死锁,死锁这种没有完全解决的方法,只能尽量预防。
预防方法
1. 一次封锁法,指的是一次性把所需要的数据全部封锁住,但是这样会扩大了封锁的范围,降低系统的并发度;
2. 顺序封锁法,指的是事先对数据对象指定一个封锁顺序,要对数据进行封锁,只能按照规定的顺序来封锁,但是这个一般不大可能的。


系统判定死锁的方法

  • 超时法:如果某个事物的等待时间超过指定时限,则判定为出现死锁;
  • 等待图法:如果事务等待图中出现了回路,则判断出现了死锁。

对于解决死锁的方法,只能是撤销一个处理死锁代价最小的事务,释放此事务持有的所有锁,同时对撤销的事务所执行的数据修改操作必须加以恢复。

参考资料

  • 《Java程序员面试笔试真题与分析》 猿媛之家 编著
  • 百度百科
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212123.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午8:46
下一篇 2026年3月18日 下午8:47


相关推荐

  • HashSet和TreeSet的区别

    HashSet和TreeSet的区别相同点 单例集合 数据不可重复 nbsp nbsp nbsp nbsp nbsp nbsp 不同点 1 底层使用的储存数据结构不同 nbsp nbsp nbsp 1 Hashset 底层使用的是 HashMap 哈希表结构储存 nbsp nbsp nbsp nbsp 2 而 Treeset 底层用的是 TreeMap 树结构储存 nbsp nbsp nbsp nbsp nbsp nbsp 不同点 2 储存的数据保存唯一方式不用 nbsp nbsp nbsp nbsp 1 Hashset 是通过复写 hashCode 方法和 equals 方法来保证的 nbsp

    2026年3月19日
    1
  • UX文案不只是logo!即梦的品牌设计为什么能形成完整闭环?

    UX文案不只是logo!即梦的品牌设计为什么能形成完整闭环?

    2026年3月13日
    2
  • java mousewheel_鼠标滚轮实现窗体图形缩放MouseWheel

    java mousewheel_鼠标滚轮实现窗体图形缩放MouseWheel功能描述 在鼠标进入一个用于绘制图形的窗体 如 Show 3D 时 滚动鼠标滚轮 使窗体中的内容能够进行放大缩小 此处以绘制 XNA 图形为例 滚动鼠标的滚轮 触发的是窗体或控件上的 MouseWheel 事件 但是在 VS2008 以及其他版本中 窗体和控件的事件列表中并不包含 MouseWheel 事件 因此对于这个事件的事件处理函数须手动创建和实现 首先在窗体的构造函数中 添加如下代码 1th

    2026年3月18日
    2
  • mybatis清空一级缓存_jvm缓存

    mybatis清空一级缓存_jvm缓存#一、前情提要长久以来,对springboot项目中缓存的使用都会有一些争论,一部分人认为缓存就应该具有延时性,即给他设置了10分钟的缓存,就应该10分钟后清理。还有一部分人认为缓存应该具有及时性(或弱及时性),即我设置了缓存后,一旦数据发生变化,缓存需要重新刷新。对于第一种观点,事实上现有的缓存结构就已经满足了,无需我们进行特殊操作,这里我们不做过多讨论。对于第二种观点,事实上现有的缓存结构也能够满足,只不过在加缓存的时候好加,可是在清理缓存的时候,我们需要手动对更新接口进行配置,可是由于项目的.

    2025年12月7日
    5
  • AI龙虾爆火 安全风险需警惕各方观点

    AI龙虾爆火 安全风险需警惕各方观点

    2026年3月15日
    2
  • conda查看及添加镜像源「建议收藏」

    conda查看及添加镜像源「建议收藏」查看源condaconfig–show-sources添加仓库condaconfig–addchannelshttps://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/condaconfig–addchannelshttps://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/condaconfig–setshow_channel_urlsyes..

    2022年10月1日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号