锁
锁的介绍
● 介绍
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
锁的分类
分类:
MySQL中的锁,按照锁的粒度分
1. 全局锁:锁定数据库中的所有表。
2. 表级锁:每次操作锁住整张表。
3. 行级锁:每次操作锁住对应的行数据。
全局锁
● 介绍
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
加锁及备份命令如下:

数据库中加全局锁,是一个比较重的操作,存在以下问题:
1. 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
2.如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
在InnoDB引擎中,我们可以在备份时加上参数 — single-transaction参数来完成不加锁的一致性数据备份。
mysqldump — single-transaction -uroot-p123456 itcast > itcast.sql
表锁
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MylSAM、InnoDB、BDB等存储引擎中。
对于表级锁,主要分为以下三类:
1. 表锁
2. 元数据锁(meta data lock,DL)
3. 意向锁
表锁:
● 表锁
对于表锁,分为两类:
1. 表共享读锁(read lock)—-读锁
2.表独占写锁(write lock)—–写锁
语法:
1. 加锁:lock tables 表名 … read/write。
2. 释放锁:unlock tables/客户端断开连接。
简单理解就是:有两个客户端,客户端(1)加上读锁,其他客户端也可以继续执行select的读操作,但不能执行其他增(insert),删(delete),改(update)操作,一直会处于阻塞状态,直到客户端(1)(加锁的客户端)执行释放锁的操作。因为读锁之间是兼容的,读写之间是互斥的。
如果客户端(1)加上了写锁,其他客户端既不能读(select),也不能执行增(insert),删(delete),改(update)操作,因为写锁和读写都是互斥的,同样也会处于阻塞状态,直到释放锁。
元数据锁
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据(表结构)的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入(改表结构)操作。为了避免DML(增删改查业务数据)与DDL(对表结构进行修改)冲突,保证读写的正确性。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。
查看元数据锁:
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;
这里的元数据锁有共享的读写锁以及排他锁。当客户端(1)开启事务执行select语句,拿到的是共享读锁,客户端(2)开启事务执行查询(select)也拿到共享写锁,或执行增(insert),删(delete),改(update)拿到共享写锁,它们之间均是共享的。
但在拿到共享读,写元数据锁之后是不能执行DDL操作的,它们之间是互斥的,必须等事务提交之后才能够执行。
二、最常见场景:DDL 被堵住,连带拖垮整个库
业务正常跑大量 SELECT / DML → 表上一直有 MDL 读锁
这时执行一条 ALTER TABLE :
1. ALTER 申请 MDL 排他锁,发现有读锁,进入等待队列
2. 后续所有新请求(哪怕只是简单 SELECT )都会排在 ALTER 后面
3. 瞬间大量会话堆积、超时,业务雪崩
这就是典型的 MDL 等待堵库。
解决:
四、怎么排查 MDL 等待
1. 看是否有 MDL 等待
SHOW PROCESSLIST;
看到 State 为 Waiting for table metadata lock 就是在等 MDL。
2. 查询元数据锁详情(MySQL 5.7+ / 8.0)
重点字段:
– LOCK_TYPE : SHARED_READ / EXCLUSIVE
– LOCK_STATUS : GRANTED (已持有)/ PENDING (等待中)
– TABLE_NAME :哪张表
3. 找到阻塞源头
用 performance_schema 或 sys 库视图,定位持有锁、长时间不提交的事务/会话。
意向锁

如图,客户端(1)开启事务,根据主键id执行update操作,会给这一行数据加上行锁,这时客户端(2)过来执行锁表的操作,这就会导致行锁和表锁发生冲突。再加表锁之前会逐行检查是否存在行锁,效率极低。这个时候就用到了意向锁。
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
添加:
1. 意向共享锁(IS):由语句select … lock in share mode添加。
2. 意向排他锁(IX):由insert、update、delete、select … for update添加。
1. 意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排它锁(write)互斥。
2. 意向排他锁(IX):与表锁共享锁(read)及排它锁(write)都互斥。意向锁之间不会互斥。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
行级锁
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
1.行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。
2.间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。
3.临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。


2. 详细对比
– INSERT / UPDATE / DELETE 的排他锁
– 触发:InnoDB 自动加锁,不需要额外关键字。
– 锁定范围:精准锁定需要修改的行,尽量缩小锁范围以提高并发。
– 作用:保证在你修改数据期间,别人不能同时修改同一行。
– 间隙锁:仅在必要时(如唯一索引失效)才会加间隙锁,默认不主动加。
– SELECT … FOR UPDATE 的排他锁
– 触发:必须手动加 FOR UPDATE 关键字才会生效。
– 锁定范围:不仅锁定查询返回的行,还会锁定这些行之间的间隙(GAP),形成 Next-Key Lock 。
– 作用:除了保证当前行不被修改,还能防止其他事务在间隙中插入新数据,彻底避免幻读。
– 间隙锁:默认就会加,即使是用唯一索引精确查询。
默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读。
1. 针对唯一索引(例如id)进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
2. InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
● 间隙锁/临键锁
默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读。
1. 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
2. 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock退化为间隙锁。
3. 索引上的范围查询(唯一索引) — 会访问到不满足条件的第一个值为止。
注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。
举例说明:
好的,我用三个具体的例子来帮你彻底搞懂间隙锁(GAP Lock)和临键锁(Next-Key Lock)的区别和应用场景。我们假设有一张表 user ,它的 id 是唯一索引,数据如下:
id name
10 张三
20 李四
30 王五
40 赵六
例1:唯一索引等值查询(记录不存在)→ 退化为间隙锁
sql
— 事务 A
BEGIN;
SELECT * FROM user WHERE id = 15 FOR UPDATE;
– 因为 id=15 这条记录不存在,InnoDB 会锁定 (10, 20) 这个间隙,这就是间隙锁。
– 它的作用是防止其他事务在 10 和 20 之间插入新数据(比如 id=12 ),避免你下次再查时出现“幻读”。
– 这时候,其他事务可以修改 id=10 或 20 的记录,但不能在 (10,20) 这个区间插入新行。
例2:普通索引等值查询(向右遍历不满足)→ 退化为间隙锁
我们给 name 字段加一个普通索引,然后执行:
sql
— 事务 A
BEGIN;
SELECT * FROM user WHERE name = '李四' FOR UPDATE;
– 因为 name 是普通索引,InnoDB 会先找到 name='李四' 的行( id=20 ),然后向右遍历到下一个不满足条件的记录( name='王五' , id=30 )。
– 这时候,它会把 (20, 30) 这个间隙也锁住,形成间隙锁。
– 这样做是为了防止其他事务在 name='李四' 和 name='王五' 之间插入新数据,避免幻读。
例3:唯一索引范围查询 → 临键锁(Next-Key Lock)
sql
— 事务 A
BEGIN;
SELECT * FROM user WHERE id > 20 AND id < 40 FOR UPDATE;
– 这个查询会匹配 id=30 的行。
– InnoDB 会锁定 (20, 30) 和 (30, 40)这两个临键区间,也就是临键锁(行锁+间隙锁的组合)。
– 它不仅锁住了 id=30 这一行,还锁住了 (20,30) 和 (30,40) 两个间隙。20,40这两行数据是没有被锁住的,这里主要看SQL语句中是>还是>=
– 这样可以防止其他事务在 20 和 40 之间插入任何新数据(比如 id=25 或 id=35 ),彻底避免幻读。
总结
1. 概述
在并发访问时,解决数据访问的一致性、有效性问题
全局锁、表级锁、行级锁
2. 全局锁
对整个数据库实例加锁,加锁后整个实例就处于只读状态
性能较差,数据逻辑备份时使用
3. 表级锁
操作锁住整张表,锁定粒度大,发生锁冲突的概率高
表锁、元数据锁、意向锁
4. 行级锁
操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低
行锁、间隙锁、临键锁



