MySQL锁都有什么?详解!

独占锁:一般指的行锁

共享锁:

独占表锁:

共享表锁:

意向排他锁:

意向共享锁:

CAS有用到硬件(操作系统)的锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

【NOTE : 针对事务才有加锁的意义。】

 

分类:MySQL中的锁,按照锁的粒度分,分为以下三类:

  1. 全局锁:锁定数据库中的所有表

  2. 表级锁:每次操作锁住整张表

  3. 行级锁:每次操作锁住对应的行数据

 

全局锁:

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。 其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

DML、DDL阻塞,DQL可以

mysqldump数据库备份工具,不是mysql的命令,应该在windows命令行输入

 flush tables with read lock;    -- 加全局锁
 mysqldump -uroot -p1234 itcat>itcast.sql -- 数据库备份,不是mysql的命令
 unlock tables; -- 释放全局锁

 

 

 

特点

数据库中加全局锁,是一个比较重的操作,存在以下问题:

如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。

如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。

 

在innodb引擎中,我们可以在备份时加上一个参数–single-transaction参数来完成不加锁的一致性数据备份,底层通过快照读实现的

 mysqldump --single-transaction --uroot -p123456 库名 > 指定存放位置/xxx.sql

如果数据库的引擎支持的事务支持可重复读的隔离级别,那么在备份数据库之前先开启事务,会先创建 Read View,然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作

因为在可重复读的隔离级别下,即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View,这就是事务四大特性中的隔离性,这样备份期间备份的数据一直是在开启事务时的数据。【不会出现不一致的情况】

–single-transaction 参数的时候,就会在备份数据库之前先开启事务。这种方法只适用于支持「可重复读隔离级别的事务」的存储引擎。

 

表级锁:

【共享锁、排他锁】只有共享锁与共享锁之间是兼容的

共享锁(读锁):S锁,只可加S锁。

排他锁(写锁):X锁,不可再加锁

此处客户端《 == 》事务,讨论事务对同一数据对象加锁

表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。

对于表级锁,主要分为以下三类:表锁、元数据锁、意向锁

 

表锁:

不是通过select …… in share mode 来加

表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。

【还会再加一个元数据锁?】

【注意!当前会话不能同时对多个表加读锁或者写锁,加了第二个锁之后,第一个锁会自动解除】--

对于表锁,分为两类:

1.表共享读锁(read lock)所有的事务(客户端)只能读(当前加锁的客户端也只能读,不能写),不能写 。

【当前客户端报错,另一个客户端阻塞】---对于当前会话来说是串行的,阻塞了读锁就释放不了了,所以写会直接失败

【另一个客户端释放锁没用,他有自己的表锁?居然可以自己再加一层锁】----因为共享锁允许多个事务并发地持有相同的锁来读取数据。【S锁可以再加S锁】

2.表独占写锁(write lock),对当前加锁的客户端,可读可写,对于其他的客户端,不可读也不可写。

 

读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。

 

语法:

 加锁:lock tables 表名 read/write
 释放锁:unlock tables / 客户端断开连接

 

 

 

元数据锁

表结构(元数据)

元数据锁(meta data lock,MDL),MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务(未提交事务)的时候,不可以对元数据进行写入操作为了避免DML与DDL冲突,保证读写的正确性。

在MySQL5.5中引入了MDL,当对一张表进行增删改查CRUD的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)

「读读」并不冲突;

SQL 语句锁类型说明
LOCK TABLES xxx READ / WRITE shared_read_only / shared_no_read_write 读写锁和写锁
SELECT, SELECT ... LOCK IN SHARE MODE【自动帮我们加上SHARED_READ。lock in share mode 是指共享锁 shared_read(共享锁) shared_readshared_write 兼容,与 exclusive 互斥
INSERT, UPDATE, DELETE, SELECT ... FOR UPDATE shared_write(共享锁?) shared_readshared_write 兼容,与 exclusive 互斥
ALTER TABLE ... exclusive(排他锁) 与其他所有 MDL 互斥(不可共存’?)

查看元数据锁:select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;

注意:

MDL 是在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的

申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。

所以为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。

 

意向锁

  • 在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」;

  • 在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」;

也就是,当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。

而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。【可以实现加行级锁】

 

【DML语句和for update会给表加上意向锁,同时会给索引添加行锁】

意向锁: 为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查

一个客户端对某一行加上了行锁,那么系统也会对其加上一个意向锁【因为是表锁,好检查】,当别的客户端来想要对其加上表锁时,便会检查意向锁是否兼容,若是不兼容,便会阻塞直到意向锁释放。

 

意向锁兼容性:

  1. 意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排它锁(write)互斥。

  2. 意向排他锁(lX):与表锁共享锁(read)及排它锁(write)都互斥意向锁之间不会互斥

 

SIX

1.意向共享锁(IS):由语句select…lock in share mode添加

2.意向排他锁(IX):由insert,update,delete,select … for update添加

 

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,

只会和共享表锁(lock tables ... read)和独占表锁(lock tables ... write)发生冲突。

 

表锁和行锁是满足读读共享、读写互斥、写写互斥的。

共享锁(S锁)满足读读共享,读写互斥。独占锁(X锁)满足写写互斥、读写互斥。

如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。

那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。

所以,意向锁的目的是为了快速判断表里是否有记录被加锁

 

查看意向锁和行锁select object_schema.object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.date.locks;

 

AUTO-INC 锁

AUTO-INC 锁是特殊的表锁机制,锁不是再一个事务提交后才释放,而是再执行完插入语句后就会立即释放

 

在插入数据时,会加一个表级别的 AUTO-INC 锁,然后为被 AUTO_INCREMENT 修饰的字段赋值递增的值,等插入语句执行完成后,才会把 AUTO-INC 锁释放掉。【其他的阻塞,从而保证插入数据时,被 AUTO_INCREMENT 修饰的字段的值是连续递增的。】

因此, 在 MySQL 5.1.22 版本开始,InnoDB 存储引擎提供了一种轻量级的锁来实现自增。

一样也是在插入数据的时候,会为被 AUTO_INCREMENT 修饰的字段加上轻量级锁,然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁

 

行级锁:

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。 InnoDB的数据是基于索引组织的【行数据是基于聚集索引组织的】,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:

  1. 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC(read commit )、RR(repeatable read)隔离级别下都支持

  2. 间隙锁(GapLock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。比如说 两个临近叶子节点为 15 23,那么间隙就是指 [15 , 23),锁的是这个间隙。

  3. 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。

行级锁的类型主要有三类:

  • Record Lock,记录锁,也就是仅仅把一条记录锁上;

  • Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身

  • Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身

 

1、行锁Record Lock

InnoDB实现了以下两种类型的行锁:

【只有共享锁之间是兼容的】

  1. 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。

  2. 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

SQL行锁类型说明
insert 排他锁 自动加锁
update 排他锁 自动加锁
delete 排他锁 自动加锁
select 不加任何锁 通过mvcc实现的无锁
select…… lock in share mode 共享锁 需要手动在SELECT之后加LOCK IN SHARE MODE
select…… for update 排他锁 需要手动在SELECT之后加FOR UPDATE

 

默认情况下,InnoDB在REPEATABLE READ(RR,可重复读)事务隔离级别运行,InnoDB使用next-key 锁进行搜索和索引扫描,以防止幻读

  1. 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁

  2. InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。【否则升级表锁】

 

 

间隙锁 Gap Lock / 临键锁Next-Key Lock

 

  1. 索引上的等值查询(唯一索引时),给不存在的记录加锁时,优化为间隙锁

  2. 索引上的等值查询(普通索引),向右遍历时最后一个【因为可能不唯一】值不满足查询需求时,next-key lock 退化为间隙锁。【对该数据前间隙,和该数到后面第一个不满足的间隙加锁】

  3. 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。

 

如果查的id = 6没有,加个行锁(空的)还有间隙锁(左开右闭,1 ~ 7是中间的间隙)

mysql解决(快照读下)的幻读。简单的select直接通过readview读正确的数据。select for update不一样,因为要读到最新的数据

 

2、间隙锁 Gap Lock

【间隙锁是开区间的,是一个在索引记录之间的间隙上的锁。不让插入删除【解决脏读】,select for update】

 

间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的

 

3、临键锁Next-Key Lock

【临键锁是是一个左开右闭的区间,比如(- ∞, 1 ] |(1, 3 ] |(3, 4 ] | (4, + ∞)。】

next-key lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。

next-key lock 是包含间隙锁+记录锁的,如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的

 

默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next-key-lock (行锁 + 间隙锁)锁进行搜索和索引扫描,以防止幻读(不可控范围的话还是控制不了幻读问题)。

 

 

 

插入意向锁

插入意向锁名字虽然有意向锁,但是它并不是意向锁,它是一种特殊的间隙锁,属于行级别锁

如果说间隙锁锁住的是一个区间,那么「插入意向锁」锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。

插入意向锁与间隙锁的另一个非常重要的差别是:尽管「插入意向锁」也属于间隙锁,但两个事务却不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。

 

附加:

这种查询会加锁的语句称为锁定读

 //先在表上加上意向共享锁(行锁),然后对读取的记录加共享锁
 select ... lock in share mode;
 
 //先表上加上意向独占锁,然后对读取的记录加独占锁
 select ... for update;
 
posted @ 2024-05-24 00:14  PgSheep  阅读(79)  评论(0编辑  收藏  举报