一文讲明白MySQL锁机制

67 阅读54分钟

首先,相信大家都知道MySQL 有锁机制,通过这些锁机制保证了MySQL的一个读写问题;但是真正能系统的讲出来却是不简单的;

我们先从锁的分类说起吧,网上对于锁的分类可谓是五花八门,有的甚至分成五六类;

今天呢我想讲的就按照最为普遍以及重要的两种分类来展开:按锁的粒度分类以及按锁的性质分类

先从简单的按照性质分类吧

按照锁的性质分类

有共享锁和排他锁

共享锁: 又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。

排他锁: 又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥

对于这两种锁,有的人喜欢称之为 S 锁 和 X 锁;

下面这张图可以很清晰的表示它们的性质:

按照锁的粒度分类

这里才是我们今天的主角,也就是种类最多最复杂的分类

首先我们可以分为 全局锁 , 表级锁 , 行级锁 , 页面锁(这个很少有人提及,我认为是比较次要的)

全局锁

这个类别比较简单,其实按照名字就可以大概的猜出来,这个锁锁的是一个很大的范围,这里锁的其实就是我们的整个数据库;

加了这个锁之后,我们的数据库就处于一个只读状态,执行非读操作通通会被阻塞;

主要就是应用于我们的数据的备份;

表级锁

表锁

顾名思义,锁住整张表,粒度最大的表级锁了

元数据锁(MDL)

首先说明一下,这个锁一般都是隐式加上的,不需要我们自己去手动加;

因为当我们对数据库表进行操作时(CRUD 或者 更改表结构),会自动给这个表加上 MDL;

其中,CRUD 加的是MDL 读锁,更改表结构加的是MDL 写锁;

意向锁

这个意向锁我们可以理解为是一种flag 锁,起到一个标记的作用;

如果表中的记录被加锁了,我们就可以直接通过这个意向锁来进行判断,而不用去遍历表中的每一行;

自增锁

这个锁算是比较冷门的表级锁了,很多的资料在讲解表级锁的时候,压根就没提到它;

我们都知道,MySQL数据库的有主键自增的策略,这个策略其实是要靠加锁来保证主键唯一且自增的;

InnoDB 存储引擎提供了个 innodb_autoinc_lock_mode 的系统变量,是用来控制选择用自增锁,还是轻量级的锁;

● 当 innodb_autoinc_lock_mode = 0,就采用 自增锁,语句执行结束后才释放锁;

● 当 innodb_autoinc_lock_mode = 2,就采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。

● 当 innodb_autoinc_lock_mode = 1:

○ 普通 insert 语句,自增锁在申请之后就马上释放;

○ 类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放

行级锁

这是MySQL中最细腻的一类锁,这也是innodb 特有的,其他引擎并无行级锁这一说;

行锁(记录锁)

这个比较简单,就是单独对某一行加上锁;

间隙锁

这个就和我们之前了解的不太一样,因为它锁的是不存在的行,也就是一个间隙;

假设表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生

(这个锁也是RR 级别下特有的 )

从区间的监督看,这个间隙锁锁的就是左开右开区间:(3,5)

临键锁

这个锁我们可以理解为为是行锁 + 间隙锁

假设,表中有一个范围 id 为(3,5] 的 临键锁,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录

这个间隙锁和临键锁确实是有点类似,我们可以这样更好的理解:
间隙锁锁的是一个范围,不包括记录本身;
临键锁锁的是一个范围 + 记录本身;
这两种锁的引入都是为了解决部分幻读的情况的!

页级锁

页面锁

这个我也不展开说了,页面锁是Berkeley DB存储引擎支持的一种锁粒度,当然,由于BDB引擎被Oracle收购的原因,因此MySQL5.1以后不再直接性的支持该引擎(需自己整合),因此页锁见的也比较少;

一页数据到底是多少呢?其实我也不大清楚,毕竟没用过BDB引擎,但我查阅了网上很多资料,估计就是只一个索引页的大小,即16KB左右