少女祈祷中...
NEKO
知识库

mysql 锁面试要点

一、锁总览类

Q1:InnoDB 有哪些锁?怎么分类?

  • 按粒度:

    • 全局锁:FLUSH TABLES WITH READ LOCK,整库只读。

    • 表锁:表级 S/X 锁、意向锁、MDL。

    • 行锁:记录锁、间隙锁、临键锁、插入意向锁。

  • 按模式:

    • 共享锁 S:读锁,LOCK IN SHARE MODE。

    • 排他锁 X:写锁,FOR UPDATE、UPDATE、DELETE。

Q2:共享锁和排他锁的区别?

  • S 锁:多个事务可同时持有,互相不阻塞。

  • X 锁:与任何锁互斥,一个事务持有,其他事务读写都阻塞。

  • 兼容矩阵:

当前\请求

S

X

S

兼容

冲突

X

冲突

冲突

Q3:意向锁是什么?为什么需要?

  • 表级锁,分意向共享锁 IS和意向排他锁 IX。

  • 作用:快速判断表中是否有行锁,避免逐行检查。

    • 当要给表加表级 S/X 锁(如 LOCK TABLES ... READ/WRITE、ALTER TABLE)时,如果表里已有行锁,就必须等待。

    • 若没有意向锁,数据库只能逐行扫描判断是否存在行锁,效率极低。

    • 有了意向锁,只需检查表级意向锁标志即可快速判断"表里是否有行级锁",无需逐行检查。

    • 意向锁是为了协调表级锁与行级锁——让表级锁能快速感知行级锁的存在。

  • 加行 S 锁前先加 IS,加行 X 锁前先加 IX。

  • 意向锁之间兼容,只与表级 S/X 锁冲突(IS 只与表级 X 冲突;IX 与表级 S、X 都冲突)。

    请求锁 \ 已有锁

    IS

    IX

    表级 S

    表级 X

    IS

    兼容

    兼容

    兼容

    冲突

    IX

    兼容

    兼容

    冲突

    冲突

    表级 S

    兼容

    冲突

    兼容

    冲突

    表级 X

    冲突

    冲突

    冲突

    冲突

Q4:MDL(元数据锁)是什么?

  • 保护表结构,防止 DDL 与 DML 冲突。

  • 读时加 MDL 读锁,写时加 MDL 写锁。

  • 长事务持有 MDL 读锁,会阻塞 DDL,导致后续查询排队。

二、行锁类型类

Q5:记录锁、间隙锁、临键锁的区别?

锁

范围

作用

记录锁

单条记录

锁住某行

间隙锁

开区间

防插入,防幻读

临键锁

记录 + 前面的间隙

RR 默认,左开右闭

插入意向锁

间隙内的插入点

插入前加,与间隙锁冲突

Q6:间隙锁的作用是什么?

  • 锁住一个区间,阻止其他事务在区间内插入。

  • 目的:防止幻读(当前读场景)。

  • 只在 RR 下生效,RC 下没有间隙锁。

Q7:临键锁和间隙锁的关系?

  • 临键锁 = 记录锁 + 间隙锁。

  • RR 下默认加临键锁,锁住记录和它前面的间隙。

  • 唯一索引等值命中时会退化为记录锁。

Q8:插入意向锁是什么?

  • 一种特殊的间隙锁,表示“想在某个间隙插入”。

  • 多个插入意向锁之间兼容,但与间隙锁冲突。

  • 作用:提高插入并发。

  • 插入意向锁(Insert Intention Lock)是 InnoDB 中一种特殊的间隙锁,专门用于 INSERT 操作。理解它的关键在于:它不是独立的一种锁模式,而是间隙锁在插入场景下的具体表现。

三、加锁规则类

Q9:RR 下不同查询怎么加锁?

场景

加锁

唯一索引等值命中

记录锁

唯一索引等值未命中

间隙锁

普通索引等值

临键锁 + 间隙锁

范围查询

临键锁

没走索引

锁全表所有行和间隙

Q10:为什么没走索引会锁全表?

  • 无法定位具体行,只能扫描并锁定扫描到的所有记录和间隙。

  • 并发急剧下降,容易死锁。

  • 所以 UPDATE/DELETE 的 WHERE 一定要走索引。

Q11:RC 和 RR 加锁有什么区别?

  • RC:没有间隙锁,只加记录锁,并发更高,但可能幻读。

  • RR:有间隙锁/临键锁,防幻读,但并发低、易死锁。

  • 很多互联网业务用 RC + binlog ROW 提升并发。

Q12:SELECT ... FOR UPDATE 和 LOCK IN SHARE MODE 区别?

  • FOR UPDATE:加 X 锁,阻塞其他读写。

  • LOCK IN SHARE MODE:加 S 锁,允许其他读,阻塞写。

  • 都是当前读,读最新数据。

四、MVCC 与锁的关系类

Q13:MVCC 能解决写写冲突吗?

  • 不能。MVCC 只解决读写冲突,让读不加锁。

  • 写写冲突仍靠锁,后写的事务阻塞。

Q14:快照读和当前读分别加锁吗?

  • 快照读:普通 SELECT,走 MVCC,不加锁。

  • 当前读:FOR UPDATE、UPDATE、DELETE,加锁读最新版本。

Q15:为什么 RR 下快照读不会幻读,当前读会?

  • 快照读复用 ReadView,看不到新插入的行。

  • 当前读读最新数据,会看到新插入的行,靠间隙锁阻止插入。

五、死锁类

Q16:死锁是怎么产生的?

  • 两个或多个事务互相持有对方需要的锁,循环等待。

  • 四个必要条件:互斥、持有并等待、不可抢占、循环等待。

Q17:如何排查死锁?

  • SHOW ENGINE INNODB STATUS,看 LATEST DETECTED DEADLOCK。

  • 查看 information_schema.innodb_trx、innodb_locks、innodb_lock_waits。

  • 开启 innodb_print_all_deadlocks 记录到 error log。

Q18:如何避免死锁?

  • 按固定顺序访问资源。

  • 小事务、快提交,减少持锁时间。

  • 走索引,避免锁太多行。

  • 降低隔离级别到 RC,减少间隙锁。

  • 设置 innodb_lock_wait_timeout,超时自动放弃。

Q19:死锁发生后 MySQL 怎么处理?

  • InnoDB 有死锁检测,发现后回滚代价小的事务。

  • 被回滚的事务报 Deadlock found,可重试。

  • 也可设 innodb_deadlock_detect=OFF,靠超时解决,但风险高。

Q20:锁等待超时和死锁的区别?

  • 锁等待超时:等锁超过 innodb_lock_wait_timeout,报超时。

  • 死锁:循环等待,InnoDB 主动检测并回滚一个。

  • 超时是被动等待,死锁是主动检测。

六、并发控制类

Q21:乐观锁和悲观锁的区别?

维度

悲观锁

乐观锁

加锁

先加锁

不加锁

实现

FOR UPDATE

版本号/CAS

适用

冲突多

冲突少

缺点

并发低、易死锁

冲突多时重试多

Q22:乐观锁怎么实现?

  • 加版本号字段,更新时 WHERE version = ?,同时 version = version + 1。

  • 影响行数为 0 说明被改过,重试。

  • 也可用时间戳或状态字段。

Q23:如何提高并发?

  • 降低隔离级别到 RC。

  • 缩短事务,快提交。

  • 走索引,减少锁范围。

  • 读写分离,读走从库。

  • 热点行拆分、排队、异步化。

Q24:热点行更新怎么优化?

  • 拆分热点行,如账户余额拆多个子账户。

  • 用队列串行化。

  • 乐观锁 + 重试。

  • 减少事务持有时间。

七、实战与陷阱题

Q25:UPDATE t SET x=1 WHERE id=1 加了什么锁?

  • id 是主键,唯一索引等值命中,加记录锁。

  • 只锁 id=1 这一行。

Q26:UPDATE t SET x=1 WHERE name='a',name 有普通索引,加什么锁?

  • 普通索引等值,加临键锁 + 间隙锁。

  • 可能锁住 name='a' 的记录和相邻间隙。

Q27:UPDATE t SET x=1 WHERE age>20,age 有索引,加什么锁?

  • 范围查询,加临键锁,锁住扫描到的区间。

  • 如果 age 无索引,锁全表。

Q28:为什么 RR 下容易死锁?

  • 间隙锁导致锁范围大,多个事务交叉加锁易循环等待。

  • 降到 RC 可减少间隙锁,降低死锁概率。

Q29:SELECT 会加锁吗?

  • 普通 SELECT 不加锁,走 MVCC。

  • FOR UPDATE / LOCK IN SHARE MODE 加锁。

Q30:表锁和行锁怎么选?

  • InnoDB 默认行锁,并发高。

  • 无索引或全表操作会退化为表锁。

  • MyISAM 只有表锁。

评论