写点什么

MySQL 各种锁详情,实战分析

用户头像
极客good
关注
发布于: 刚刚

表锁(锁定整个表)


=============


表级锁是 mysql 锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。


该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。


所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。


表锁被大部分的 mysql 引擎支持,MyISAM 和 InnoDB 都支持表级锁。


MyISAM 只是支持表锁,因此性能相对 Innodb 来说相对降低,而 Innodb 也支持表锁,但是默认的行锁,而且只有在查询或者其他 SQL 语句通过索引才会使用行锁。


行锁(锁定一行)


============


行锁的是 mysql 锁中粒度最小的一种锁,因为锁的粒度很小,所以发生资源争抢的概率也最小,并发性能最大,但是也会造成死锁,每次加锁和释放锁的开销也会变大。


目前主要是 Innodb 使用行锁,Innodb 也是 mysql 在 5.5.5 版本之后默认使用的存储引擎。


「行锁按照使用方式也氛围共享锁(S 锁或者读锁)和排它锁(X 锁或者写锁)」


共享锁(S 锁,读锁)


==============


使用说明:若事务 A 对数据对象 1 加上 S 锁,则事务 A 可以读数据对象 1 但不能修改,其他事务只能再对数据对象 1 加 S 锁,而不能加 X 锁,直到事务 A 释放数据对象 1 上的 S 锁。


这保证了其他事务可以读数据对象 1,但在事务 A 释放数据对象 1 上的 S 锁之前不能对数据对象 1 做任何修改。


用法:


select?...?lock?in?share?mode;


----共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。


排它锁(X 锁,写锁)


==============


?



使用说明:若事务 A 对数据对象 1 加上 X 锁,事务 A 可以读数据对象 1 也可以修改数据对象 1,其他事务不能再对数据对象 1 加任何锁,直到事务 A 释放数据对象 1 上的锁。


这保证了其他事务在事务 A 释放数据对象 1 上的锁之前不能再读取和修改数据对象 1。


select?...?for?update


----排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁


意向共享锁(IS)和意向排它锁(IX)


=======================


「释义:」


「意向共享锁(IS)」:事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁。


「意向互斥锁(IX)」:事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁。


意向共享锁和意向排它锁总称为意向锁。意向锁的出现是为了支持 Innodb 支持多粒度锁。


首先,意向锁是表级别锁。


理由:当我们需要给一个加表锁的时候,我们需要根据意向锁去判断表中有没有数据行被锁定,以确定是否能加成功。


如果意向锁是行锁,那么我们就得遍历表中所有数据行来判断。如果意向锁是表锁,则我们直接判断一次就知道表中是否有数据行被锁定了。


所以说将意向锁设置成表级别的锁的性能比行锁高的多。


有了意向锁之后,前面例子中的事务 A 在申请行锁(写锁)之前,数据库会自动先给事务 A 申请表的意向排他锁。


当事务 B 去申请表的写锁时就会失败,因为表上有意向排他锁之后事务 B 申请表的写锁时会被阻塞。


所以,意向锁的作用就是:


当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。


如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。


乐观锁


=======


乐观锁不是数据库自带的,需要我们自己去实现。


乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。


通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加 1。


也就是先查询出那条记录,获取出 version 字段,如果要对那条记录进行操作(更新),则先判断此刻 version 的值是否与刚刚查询出来时的 version 的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将 version 字段的值加 1;


如果更新时发现此刻的 version 值与刚刚获取出来的 version 的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。


使用实例:


1.?SELECT?data?AS?old_data,?version?AS?old_version?FROM?…;


2.?根据获取的数据进行业务操作,得到 new_data 和 new_version


3.?UPDATE?SET?data?=?new_data,?version?=?new_version?WHERE?version?=?old_version


if?(updated?row?>?0)?{


//?乐观锁获取成功,操作完成


}?else?{


//?乐观锁获取失败,回滚并重试


}


「优点」


从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。


「缺点」


乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。


在系统设计阶段,应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。


总结:读用乐观锁,写用悲观锁。


悲观锁


=======


悲观锁介绍(引自百科):


?



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


悲观锁的实现:首先实现悲观锁时,我们必须先使用 set autocommit=0; 关闭 mysql 的 autoCommit 属性。因为我们查询出数据之后就要将该数据锁定。


关闭自动提交后,我们需要手动开启事务。


//1.开始事务


begin;?或者?start?transaction;


//2.查询出商品信息,然后通过 for?update 锁定数据防止其他事务修改


select?status?from?t_goods?where?id=1?for?update;


//3.根据商品信息生成订单


insert?into?t_orders?(id,goods_id)?values?(null,1);


//4.修改商品 status 为 2


update?t_goods?set?status=2;


//4.提交事务


commit;?--执行完毕,提交事务


上述就实现了悲观锁,悲观锁就是悲观主义者,它会认为我们在事务 A 中操作数据 1 的时候,一定会有事务 B 来修改数据 1,所以,在第 2 步我们将数据查询出来后直接加上排它锁(X)锁


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


,防止别的事务来修改事务 1,直到我们 commit 后,才释放了排它锁。


「优点」


保证了数据处理时的安全性。


「缺点」


加锁造成了开销增加,并且增加了死锁的机会。降低了并发性。


**「乐观锁」**更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。


下面三种锁都是 innodb 的行锁,前面我们说过行锁是基于索引实现的,一旦加锁操作没有操作在索引上,就会退化成表锁。


间隙锁(Next-Key 锁)


==================

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
MySQL各种锁详情,实战分析