众所周知,mysql 中有很多种锁的概念,如行锁、乐观锁、排它锁等等,本文对此进行归纳。

锁的类型

加锁类型的不同,表现在对竞争资源的处理有不同的处理思想。

假设有这样的场景,A、B 两个用户要同时将机票价格从 2 元改成 6、3 元。

乐观锁

取数据时,假定别人不会修改该数据,因此只会对数据做简单的校验,并不对数据上锁。

但如果有大量进程修改某行数据,就会导致大部分进程写失败。而写失败后如果有重试,将会耗费大量资源。因此,乐观锁通常用于读多写少的场景。

悲观锁

取数据时总是假定其他人会修改,因此取数据前会上锁。如果有多个进程并发修改数据,最终只能有一个进程成功上锁并修改数据。

锁的互斥属性

共享锁(读锁、S 锁)

不同的人能共享同一份资源。

如多个事务可以同时查看某行数据,对该行数据上共享锁,防止数据被改。

在 mysql 中通常使用语句 select * from user where id = 1 LOCK IN SHARE MODE; 来设置共享锁。

排它锁(写锁、X 锁)

不同的人不能共享同一份资源。

如多个事务可以同时修改某行数据,要对修改的数据上排它锁,多个排它锁之间会相互排斥,不能兼容。

在 mysql 中通常使用语句 select * from user where id = 1 FOR UPDATE; 来设置排他锁。

锁的兼容性

对于同一行数据,有的类型的锁可以并存,有的则不行,具体如下:

共享锁 排它锁
共享锁 ×
排它锁 × ×

锁的粒度

全局锁

mysql 中进行数据备份时,可使用全局锁锁定整个库,Flush tables with read lock ,防止行数据、表被修改。

表锁

表数据锁

lock tables … read/write; 将表中数据锁住,使得不能修改表中的数据。

元数据锁(MDL)

表的元数据(Meta Data),是指用于描述表的结构、属性等信息。

当对表进行结构修改时,如字段的增删改、表属性的变更,会加 MDL 写锁。

当对表中数据增删改查时,会对表加 MDL 读锁,以防表中列结构的变动。

MDL 锁的互斥性如下:

读锁 写锁
读锁 ×
写锁 × ×

行锁

在 InnoDB 中,数据是以聚簇索引组织的,因此 InnoDB 的行锁加在聚簇索引的。而对于非聚簇索引,锁均会加在二级索引、主键索引上。

记录锁(Record Lock)

1
select * from T where id in (1,3,5) for update ;

该行语句,对 id 为 1、3、5 的数据行上了排它记录锁,该锁是的粒度是某几行数据。

间隙锁(Gap Lock)

1
select * from T where id < 10 for update ;

该语句,对 id 小于 10 的数据上锁,解锁前无法插入、修改 id 小于 10 的数据。

临键锁(Next Key Lock)

临键锁 = 记录锁 + 间隙锁,例如:

1
select * from T where id <= 10 for update ;

参考资料

极客时间,丁奇《MySQL 45 讲》