MySQL锁都有什么?详解!
锁
独占锁:一般指的行锁
共享锁:
独占表锁:
共享表锁:
意向排他锁:
意向共享锁:
CAS有用到硬件(操作系统)的锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
【NOTE : 针对事务才有加锁的意义。】
分类:MySQL中的锁,按照锁的粒度分,分为以下三类:
-
全局锁:锁定数据库中的所有表。
-
表级锁:每次操作锁住整张表。
-
行级锁:每次操作锁住对应的行数据。
全局锁:
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的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_read 和 shared_write 兼容,与 exclusive 互斥 |
INSERT , UPDATE , DELETE , SELECT ... FOR UPDATE |
shared_write (共享锁?) |
与 shared_read 和 shared_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中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
一个客户端对某一行加上了行锁,那么系统也会对其加上一个意向锁【因为是表锁,好检查】,当别的客户端来想要对其加上表锁时,便会检查意向锁是否兼容,若是不兼容,便会阻塞直到意向锁释放。
意向锁兼容性:
-
意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排它锁(write)互斥。
-
意向排他锁(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的数据是基于索引组织的【行数据是基于聚集索引组织的】,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
-
行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC(read commit )、RR(repeatable read)隔离级别下都支持。
-
间隙锁(GapLock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。比如说 两个临近叶子节点为 15 23,那么间隙就是指 [15 , 23),锁的是这个间隙。
-
临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。
行级锁的类型主要有三类:
Record Lock,记录锁,也就是仅仅把一条记录锁上;
Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身;
Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。
1、行锁Record Lock
InnoDB实现了以下两种类型的行锁:
【只有共享锁之间是兼容的】
-
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
-
排他锁(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 锁进行搜索和索引扫描,以防止幻读。
-
针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
-
InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。【否则升级表锁】
间隙锁 Gap Lock / 临键锁Next-Key Lock
-
索引上的等值查询(唯一索引时),给不存在的记录加锁时,优化为间隙锁。
-
索引上的等值查询(普通索引),向右遍历时最后一个
-
索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
如果查的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;