跳到主要内容

MySQL 什么时候需要手动加锁

1. 基础概念题

Q1: 请解释 MySQL 中的锁类型有哪些?它们的作用是什么?

答案要点:

  • 共享锁(S锁):事务T1对数据A加共享锁后,事务T2可以读数据A,但不能修改
  • 排他锁(X锁):事务T1对数据A加排他锁后,事务T2既不能读也不能修改数据A
  • 意向共享锁(IS锁):事务在获取任何S锁之前,必须先在表上加IS锁
  • 意向排他锁(IX锁):事务在获取任何X锁之前,必须先在表上加IX锁

意向锁的作用流程图:

解释说明: 意向锁的存在使得表级锁操作不需要逐行检查是否存在行级锁冲突,提高了锁管理效率。

Q2: 什么是快照读和当前读?它们在什么情况下会被使用?

答案要点:

  • 快照读:读取数据库记录的快照版本,不加锁,基于MVCC实现
  • 当前读:读取数据的最新版本,需要加锁

快照读vs当前读的使用场景:

2. 加锁算法面试题

Q3: 请详细解释 MySQL 的三种加锁算法

答案要点:

  1. Record Locks(行锁)

    • 锁定索引记录,注意是锁索引而不是行
    • 所有行锁最终都会落到聚簇索引上
  2. Gap Locks(间隙锁)

    • 锁定索引间隙,防止其他事务插入数据
    • 只在RR和Serializable级别下存在
    • RC和RU级别不使用间隙锁
  3. Next-Key Locks

    • Record Lock + Gap Lock的组合
    • 锁定记录及其前面的间隙

间隙锁范围示例: 假设索引包含值:10, 11, 13, 20

Q4: 在什么情况下会出现"锁表"现象?这是真的表锁吗?

经典错误观点纠正:

"只有通过索引条件检索数据,InnoDB才使用行级锁,否则使用表锁"

正确答案:

  1. 不是真正的表锁,而是通过Next-Key Locks(行锁+间隙锁)实现
  2. 只在RR和Serializable级别成立,RC和RU级别只锁行
  3. 具体过程

3. 事务隔离级别与锁关系

Q5: 不同事务隔离级别下,以下SQL的加锁情况如何?

-- 给定表结构和数据
-- pId为主键,num列无索引
-- 数据:(1,'aaa',100), (2,'bbb',200), (3,'bbb',300), (7,'ccc',200)

SELECT * FROM table WHERE num = 200 FOR UPDATE;

分析流程图:

详细答案:

隔离级别索引情况加锁结果
RC/RU无索引pId=2,7的聚簇索引行锁
RC/RU有索引num索引锁 + pId=2,7聚簇索引锁
RR/Serializable无索引全表行锁 + 全部间隙锁
RR/Serializable有索引num索引锁 + 间隙锁 + 聚簇索引锁

4. 幻读相关面试题

Q6: RR隔离级别真的解决了幻读问题吗?请举例说明

答案: RR级别并未完全解决幻读问题

幻读场景时序图:

解决方案:

-- 使用当前读避免幻读
SELECT * FROM table WHERE id > 10 LOCK IN SHARE MODE;
-- 或
SELECT * FROM table WHERE id > 10 FOR UPDATE;

Q7: 为什么有了MVCC还需要间隙锁?

MVCC vs 间隙锁对比:

5. 综合应用题

Q8: 在高并发场景下,如何选择合适的锁策略?

决策流程图:

Q9: 请分析以下场景的死锁原因并给出解决方案

场景:

-- 事务A
BEGIN;
SELECT * FROM order WHERE id = 1 FOR UPDATE;
SELECT * FROM user WHERE id = 10 FOR UPDATE;

-- 事务B
BEGIN;
SELECT * FROM user WHERE id = 10 FOR UPDATE;
SELECT * FROM order WHERE id = 1 FOR UPDATE;

死锁分析时序图:

解决方案:

  1. 统一加锁顺序:总是先锁order表,再锁user表
  2. 减少锁持有时间:拆分大事务
  3. 使用超时机制:设置innodb_lock_wait_timeout
  4. 业务层面优化:避免跨表事务

Q10: 实际项目中遇到锁等待超时,你会如何排查和解决?

排查流程图:

References