加锁的目的就是保证共享资源在任意时间里,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。
按操作数据的粒度分类:全局锁、表级锁、行锁
按数据操作的类型分类:
按锁的级别分类:
高级的锁都会由低级锁来实现,比如读写锁既可以选择互斥锁实现,也可以基于自旋锁实现。
执行后,整个数据库就处于只读状态了,DQL查询可用,DML和DDL不可用。
场景:
全局锁主要应用于做全库逻辑备份,这样就不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样的情况。
缺点: 粒度太大,造成业务停滞
上锁:flush tables with read lock;
解锁:unlock tables;
InnoDb和MyISAM都支持;开销小,加锁快,粒度大,冲突概率高,并发度低下;
上锁:
lock tables 表名 read;
(读锁)
lock tables 表名 weite;
(写锁)
解锁:
unlock tables;
注意:当上读锁时,上锁的这个会话中也不能对该表进行写(DML、DDL)操作
InnoDb引支持行级锁,MyISAM不支持,开销大,加锁慢,粒度小,冲突概率低,并发度高;
上锁:
select... where... lock in share mode;
select... where... for update;
当已经有一个线程加锁后,其他线程加锁则就会失败,互斥锁和自旋锁对于加锁失败后的处理方式是不一样的:
互斥锁:
互斥锁是一种「独占锁」,比如当线程 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