大家好,我是 javapub。

技多不压身!

今天一个朋友找我吐槽,说自己平时在工作中几乎用不到需要上锁的场景,就算有也只是并发很小、或者直接从有业务设计上就规避掉了。但一到面试,都是各种锁题,很头疼。

面试造火箭的现象是这个行业的一个常态,而且掌握底层知识真的可以帮助我们更好的做好技术设计。所以,我劝他不要抵触,等学会的知识点多了融会贯通,学其他的东西也会快很多。

image-20240527184946810

2.1 共享锁/排它锁

共享锁(S锁)和排它锁(X锁)是最基础的锁类型,用于操作对数据的读取和写入。

通过名字,我们也可以看出这两个锁的作用。当事务要读取一条记录时,先获取该记录的S锁;当事务要改动一条记录时,先获取该记录的X锁。

img

通过这个图可以知道,什么情况下可以获得 S 锁和 X 锁。可以看到,只有都是共享锁时,才可以同时在一行记录加锁。

例子:

加共享锁,共享锁允许其他事务读取这些行,但不允许其他事务修改或删除这些行,直到当前事务结束。

SELECT ... LOCK IN SHARE MODE; 加共享锁
---
SELECT * FROM user WHERE id=1 LOCK IN SHARE MODE;

加排他锁,相当于是独占记录。当一个事务对某行数据加上排他锁后,其他事务既不能读取也不能修改这些数据,直到持有排他锁的事务结束。

SELECT ... FOR UPDATE; 加排他锁
---
START TRANSACTION;
SELECT * FROM table_name WHERE condition FOR UPDATE;
-- 执行一些更新操作
UPDATE table_name SET column = value WHERE condition;
COMMIT;

2.2 行锁/临键锁

行锁

行锁通常在执行涉及单个行的操作时自动应用,例如 SELECT 语句中的 FOR UPDATE 子句,这会为查询结果中的每行添加排他锁。也就是上面说到的例子。

START TRANSACTION;
SELECT * FROM table_name WHERE id = 1 FOR UPDATE;
-- 在此事务中,id为1的行将被锁定,其他事务不能修改或读取这行
COMMIT;

临键锁

临键锁结合了记录锁和间隙锁,用于行级锁定和范围查询,防止幻读。我们一般无法判断是行锁还是临键锁,都是行锁的形式,由存储引擎在执行查询时自动管理决定的。

START TRANSACTION;
SELECT * FROM table_name WHERE id BETWEEN 1 AND 10 FOR UPDATE;
-- 这将锁定id在1到10之间的所有行,以及可能的下一个键值
COMMIT;

2.3 意向锁

意向锁是针对多粒度锁定协议的系统,比如行粒度锁、表粒度锁。当一个事务打算在某个细粒度上请求锁(共享锁|排他锁),它会首先在更粗力度上设置意向锁。

意向共识锁 - 简称 IS

意向排他锁 - 简称 IX

例子:如果一个事务要对一张表加排他锁,它会先在表级别加上意向排他锁,然后对表中的特定行加排他锁。

img

2.4 间隙锁/插入意向锁

间隙锁和插入意向锁针对实现了多版本并发控制(MVCC)的系统中,如 MySQL 的 InnoDB 存储引擎。

间隙锁(Gap Locks):

间隙锁是一种行锁,它锁定一个范围内的间隙,但不锁定该范围内的任何具体行。间隙锁主要用于防止其他事务在这个间隙中插入新的行,从而维护数据的顺序性和一致性。 间隙锁通常在执行范围查询并加上共享或排他锁时自动应用。例如,如果一个事务执行了 SELECT ... WHERE index_column BETWEEN x AND y LOCK IN SHARE MODE ,InnoDB 会在索引列 x 和 y 之间的间隙上设置间隙锁,防止其他事务在这个范围内插入新行。

示例:

START TRANSACTION;
SELECT * FROM table_name WHERE id BETWEEN 10 AND 20 LOCK IN SHARE MODE;
-- 在id为10到20的范围内设置间隙锁
COMMIT;

插入意向锁(Insert Intention Locks):

插入意向锁是一种特殊的间隙锁,它表明一个事务有意向在某个间隙中插入新行。 当一个事务想要在一个已经被其他事务加上间隙锁的范围内插入新行时,它会首先在该范围内设置一个插入意向锁。

插入意向锁允许多个事务保留在特定间隙中插入新行的意图,而不直接与间隙锁冲突。这样,当间隙锁被释放时,持有插入意向锁的事务可以继续执行插入操作。

示例:

START TRANSACTION;
-- 假设另一个事务已经在id为10到20的范围内设置了间隙锁
SELECT * FROM table_name WHERE id = 15 FOR UPDATE;
-- 这将设置一个插入意向锁,表明事务有意向在id为10到20的范围内插入新行
COMMIT;

2.5 自增锁

自增锁(Auto-Increment Locks,简称:AI Locks)是MySQL数据库中InnoDB存储引擎特有的一种锁机制,它与自增字段(AUTO_INCREMENT)相关联。AUTO_INCREMENT 你一定不陌生,我们在建表时多数情况都会让主键 id 自增来生成唯一序列。

当一个表中包含自增字段时,InnoDB 会使用自增锁来确保在并发环境下,自增字段生成的值是唯一的,并且连续的。

示例:

START TRANSACTION;
INSERT INTO table_name (auto_increment_column, other_columns) VALUES (NULL, 'value1');
-- InnoDB分配自增值并锁定它
COMMIT;
-- 自增锁在事务提交时释放

在这个示例中,auto_increment_column 是一个自增字段。当事务提交时,InnoDB 会分配一个新的自增值给插入的行,并在事务提交时释放自增锁。

2.6 外键锁

外键(Foreign Key)是一种数据库完整性约束,它用于维护两个表之间的链接,并确保引用的数据的完整性。

外键锁顾名思义就是针对外键的。外键锁并不是一个标准的锁类型,而是指与外键约束相关的锁定行为,这些行为确保在执行涉及外键的插入或更新操作时,数据库的完整性不被破坏。

外键锁这个术语并不是用来描述一种特定的锁类型,而是用来描述与外键约束相关的锁定行为。数据库系统会自动处理这些锁定,以确保数据的完整性和一致性。

2.7 表锁/页锁

表锁和页锁是两种不同粒度的锁,

表锁(Table Locks)

表锁是锁定整个表的锁,这意味着在锁定期间,没有其他事务可以对这张表进行读写操作。表锁通常用于批量操作,如全表扫描或全表更新,以及在不需要频繁锁定和解锁单个行的场景中。

特点:

  • 粒度较大:表锁影响整个表的所有数据,因此粒度较大。
  • 冲突较少:由于锁定了整个表,减少了锁冲突的可能性,但在高并发环境下可能导致其他事务长时间等待。
  • 使用场景:适用于全表操作,如全表备份或全表删除。

示例:

LOCK TABLES table_name WRITE;
-- 在此期间,其他事务不能访问table_name
UNLOCK TABLES;

页锁(Page Locks)

页锁是锁定数据库中的一个“页”的锁。在许多数据库系统中,数据是按页存储的,每页包含一定数量的行。页锁允许多个事务同时访问不同的页,从而提供比表锁更细粒度的并发控制。

特点:

  • 粒度较小:页锁锁定的是数据页,而不是整个表,因此粒度较小。
  • 并发性更好:允许多个事务并发访问不同的数据页,提高了并发性能。
  • 使用场景:适用于需要较高并发性能的场景,尤其是在大型表上进行部分数据的读写操作。

注意:页锁通常由数据库管理系统自动管理,不需要用户显式操作。例如,在InnoDB存储引擎中,虽然页锁不是用户可以直接控制的锁类型,但InnoDB会根据需要自动在页级别上应用锁。

总结: 粒度:行锁 < 页锁 < 表锁(从细到粗)。