Mysql中常见的锁

加锁的目的就是保证共享资源在任意时间里,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。

分类:

操作数据的粒度分类:全局锁、表级锁、行锁

数据操作的类型分类:

  1. 悲观锁读锁(共享锁),写锁(排他锁)都不支持多线程并发;表级锁和行级锁都有读锁和写锁;
  2. 乐观锁支持多线程并发,事务不需要排队,都可以对该数据进行尝试修改,不过该数据需要有版本号,只有修改前和修改后的版本号一致才会修改成功,否则就会回滚。(先修改再验证) ,如在线文档
    只有在【冲突概率非常低】,【且加锁成本非常高的场景时】,才考虑使用乐观锁。

锁的级别分类:

  1. 高级锁:读锁、写锁
  2. 低级锁: 互斥锁、自旋锁 (都是悲观锁)

高级的锁都会由低级锁来实现,比如读写锁既可以选择互斥锁实现,也可以基于自旋锁实现。

1. 乐观锁

乐观锁不是数据库自带的,需要我们自己去实现。
乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不加锁,而在进行更新后,再去判断是否有冲突了。

场景:乐观并发控制多数用于数据争用不大、冲突较少的环境中,这种环境中,偶尔回滚事务的成本会低于读取数据时锁定数据的成本,因此可以获得比其他并发控制方法更高的吞吐量;

优点:乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁
缺点:不适用于冲突概率比较大的情况;

实现
在表中的数据进行操作时(更新),先给数据表加一个版本号(version)字段,每操作一次,将那条记录的版本号加1;
也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻最新的version版本号是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;反之 则不进行更新操作。

1.查询出商品信息:
 select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status2update t_goods set status=2,version=version+1
	 where id=#{id} and version=#{version};
 

2. 全局锁

执行后,整个数据库就处于只读状态了,DQL查询可用,DML和DDL不可用。

场景

全局锁主要应用于做全库逻辑备份,这样就不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样的情况。

缺点: 粒度太大,造成业务停滞;

上锁:flush tables with read lock;
解锁:unlock tables;

3. 表锁

InnoDb和MyISAM都支持;开销小,加锁快,粒度大,冲突概率高,并发度低下;

上锁:
lock tables 表名 read; (读锁)
lock tables 表名 weite; (写锁)

解锁:
unlock tables;

注意:当上读锁时,上锁的这个会话中也不能对该表进行写(DML、DDL)操作

  • 读锁(共享锁)
    读锁是共享的,相互不阻塞,多个用户【同一时刻】可以读取同一个资源,但上读锁的这个用户也不能对表进行写!即所有用户都只能读(sql)

  • 写锁(排他锁)
    写锁是排他锁,一个用户写锁会阻塞其他用户,其他用户不能读也不能写;(和行级的排他锁不一样!)

4. 行锁

InnoDb默认使用行级锁,MyISAM不支持,开销大,加锁慢,粒度小,冲突概率低,并发度高;

上锁:
select... where... lock in share mode; 行级共享锁

select... where... for update; 行级排他锁

4.1 共享锁

允许不同事务之间加共享锁读取,但不允许其它事务修改或者加入排他锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改

:对于事务A,如果对某行数据加了共享锁的话,接后的语句进行了更新操作,会自动将这行数据由共享锁变为排他锁,由于排他锁无法与共享锁共存,所以事务B无法对这行数据进行任何加锁操作,只能等待事务A提交解锁;


session1:

start transaction;
select * from test where id = 1 lock in share mode;

session2:

start transaction;
select * from test where id = 1 lock in share mode;

此时 session1 和 session2 都可以正常获取结果,那么再加入 session3 排他锁读取尝:

session3:

start transaction;
select * from test where id = 1 for update;

在 session3 中则无法获取数据,直到超时或其它事物 commit
超时报错信息::[Err] 1205 - Lock wait timeout exceeded; try restarting transaction

4.2 排他锁

排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就必须等待该事务处理完才可以处理那行数,获取锁的事务是可以对数据就行读取和修改;

悲观并发控制实际上是”先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的概率;

优点:悲观并发控制实际上是”先取锁再访问”的保守策略,为数据处理的安全提供了保证。
缺点:但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的可能;

注意

  1. Mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁DQL语句默认不会加任何锁类型

    所以加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据 ! 因为select 普通查询没有任何锁机制。(和表级排他锁不同)

  2. 行级锁都是基于索引的,如果一条SQL语句用则会使用表级锁把整张表锁住,即上升为表级锁!

行级排他锁和索引的关系 ※

行级锁都是基于索引的如果一条SQL语句用不到索引则会使用表级锁把整张表锁住,即上升为表级锁!
(1)delete from msg where id=2;
id是主键(聚簇索引),因此直接用排他锁 锁住整行记录即可 ;
在这里插入图片描述

(2) delete from msg where token=’ cvs’;
由于token是二级索引,因此首先锁住二级索引(两行),接着会用排他锁 锁住相应主键所对应的记录;
在这里插入图片描述
(3) delete from msg where message=‘订单号是多少’
message 没有索引,所以走的是全表扫描过滤。这时表上的各个记录都将添加上X锁。
在这里插入图片描述
由此可得出,建立聚簇索引,将大幅提升该查询语句的性能,降低了锁定资源的时间,同时也减少了锁定资源的范围,这样就降低了锁资源循环等待事件发生的概率

5. 低级锁:互斥锁、自旋锁

当已经有一个线程加锁后,其他线程加锁则就会失败,互斥锁和自旋锁对于加锁失败后的处理方式是不一样的:

  • 互斥锁加锁失败后,线程会释放 CPU ,给其他线程;
  • 自旋锁加锁失败后,线程会忙等待,直到它拿到锁;

互斥锁:
互斥锁是一种「独占锁」,比如当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放手中的锁,线程 B 加锁就会失败,于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,自然线程 B 加锁的代码就会被阻塞态。
对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为「睡眠」状态,【等到锁被释放后】,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执行。所以,互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,虽然简化了使用锁的难度,但是线程切换存在一定的开销。

自旋锁:
使用自旋锁的时候,当发生多线程竞争锁的情况,加锁失败的线程会「忙等待」,
自旋锁是最比较简单的一种锁,一直自旋,利用 CPU 周期,直到锁可用,
但如果被锁住的代码执行时间过长,自旋的线程会长时间占用 CPU 资源,

比较:
旋锁与互斥锁使用层面比较相似,但实现层面上完全不同:当加锁失败时,互斥锁用 「线程切换」 来应对,自旋锁则用 「忙等待」 来应对。

它俩是锁的最基本处理方式,更高级的锁都会选择其中一个来实现,比如读写锁既可以选择互斥锁实现,也可以基于自旋锁实现,

场景:
互斥锁加锁失败会切换线程来应对,会增加开销;如果被锁住的代码的执行时间短,应该选择自旋锁,忙等待时间也短,开销小;

参考:
https://blog.csdn.net/qq_34827674/article/details/108608566
https://xiaolincoding.com/mysql/lock/mysql_lock.html#%E5%85%A8%E5%B1%80%E9%94%81
https://blog.csdn.net/cc2415/article/details/95734793
https://blog.csdn.net/java_lifeng/article/details/105951334

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值