返回首页面试题

面试必备:MySQL 锁机制深度解析

2026年03月25日9 min read

面试必备:MySQL 锁机制深度解析

为什么需要锁?

多事务并发时,锁用来协调对同一数据的访问,保证隔离性和一致性。

事务 A                      事务 B
─────────────────────────────────────────
SELECT balance              SELECT balance
← 1000                      ← 1000
                            UPDATE balance = 1100
UPDATE balance = 900        
← 1000-1000=900
                            ← 1000+1000=2000  ← 错误!丢失了 A 的更新

MySQL 锁分类

按粒度分:
├── 表锁(Table Lock)
├── 行锁(Row Lock)
└── 页面锁(Page Lock,InnoDB 不常用)

按类型分:
├── 共享锁(S Lock)
├── 排他锁(X Lock)
├── 意向锁(IX、IS)
└── Record Lock、Gap Lock、Next-Key Lock

按特性分:
├── 乐观锁
└── 悲观锁

共享锁 vs 排他锁

共享锁(S Lock)

允许事务读取数据,其他事务也可以加共享锁读取。

-- 加共享锁
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;
-- 或
SELECT * FROM user WHERE id = 1 FOR SHARE;  -- MySQL 8.0+

-- 其他事务也可以:
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;  -- ✓ 可以
UPDATE user SET name = 'new' WHERE id = 1;           -- ✗ 阻塞(排他锁冲突)

排他锁(X Lock)

允许事务读写数据,其他事务不能加任何锁。

-- 加排他锁
SELECT * FROM user WHERE id = 1 FOR UPDATE;
-- 或
UPDATE user SET name = 'new' WHERE id = 1;  -- 自动加排他锁

-- 其他事务:
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;  -- ✗ 阻塞
UPDATE user SET name = 'another' WHERE id = 1;       -- ✗ 阻塞
锁组合兼容
S + S✓ 兼容
S + X✗ 不兼容
X + X✗ 不兼容

行锁 vs 表锁

InnoDB 行锁

只锁住选中的行,并发性能高。

-- 行锁示例
SELECT * FROM user WHERE id = 1 FOR UPDATE;  -- 只锁 id=1 这一行
UPDATE user SET name = 'new' WHERE id = 2;    -- id=2 可以正常修改(不阻塞)

行锁特点

  • 开销大,加锁慢
  • 锁定粒度小
  • 死锁概率高
  • 支持 MVCC

MyISAM 表锁

锁住整张表,并发性能低。

LOCK TABLES user WRITE;  -- 排他表锁
UNLOCK TABLES;

表锁特点

  • 开销小,加锁快
  • 锁定粒度大
  • 无死锁
  • 不支持事务

InnoDB 的行锁算法

1. Record Lock(记录锁)

锁住某一条索引记录。

SELECT * FROM user WHERE id = 1 FOR UPDATE;
-- 锁住 id=1 的索引记录

2. Gap Lock(间隙锁)

锁住索引记录之间的间隙,防止插入。

SELECT * FROM user WHERE id BETWEEN 1 AND 10 FOR UPDATE;
-- 锁住 (1, 10) 区间
-- 其他事务无法插入 id 在 1~10 之间的记录

作用:防止幻读。

3. Next-Key Lock(临键锁)

Record Lock + Gap Lock 的组合。

索引:1, 5, 10, 15, 20

SELECT * FROM user WHERE id = 5 FOR UPDATE;
-- 锁住:
--   Record Lock: id=5
--   Gap Lock: (1, 5) 和 (5, 10)
--   Next-Key Lock: (-∞, 5] 和 (5, 10]

4. Insert Intention Lock(插入意向锁)

INSERT 操作在等待时产生的锁。

-- 事务 A
INSERT INTO user VALUES (5);  -- 等待 Gap Lock

-- 事务 B
INSERT INTO user VALUES (5);  -- 也等待 Gap Lock
-- 两者都产生 Insert Intention Lock,互不阻塞

意向锁(Intention Lock)

InnoDB 为了在表锁和行锁之间建立桥梁。

-- 事务 A:获取行锁
SELECT * FROM user WHERE id = 1 FOR UPDATE;  -- id=1 行被锁定

-- 事务 B:尝试获取表锁
LOCK TABLES user WRITE;  -- 需要检查是否有行锁

-- InnoDB 检查:存在 IX 意向锁吗?
-- IX:有意获取行排他锁
-- IS:有意获取行共享锁
锁类型说明
IS(意向共享锁)事务有意对表中的行加共享锁
IX(意向排他锁)事务有意对表中的行加排他锁

兼容关系

XIXSIS
X
IX
S
IS

乐观锁 vs 悲观锁

悲观锁

假设并发冲突,先加锁再操作。

-- 悲观锁
SELECT * FROM user WHERE id = 1 FOR UPDATE;
-- 修改
UPDATE user SET balance = balance - 100 WHERE id = 1;
COMMIT;

乐观锁

假设冲突少,先操作再检查版本。

-- 乐观锁:用版本号
SELECT id, balance, version FROM user WHERE id = 1;
-- 结果:id=1, balance=1000, version=1

UPDATE user 
SET balance = balance - 100, version = version + 1 
WHERE id = 1 AND version = 1;  -- 检查版本

-- 如果 version 不是 1,说明被其他事务修改了,失败
-- 失败后重试(乐观锁失败处理)

死锁(Dead Lock)

什么是死锁?

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

事务 A                      事务 B
─────────────────────────────────────────
BEGIN;
UPDATE user SET name='A' WHERE id=1;  -- 锁住 id=1
                                        BEGIN;
                                        UPDATE user SET name='B' WHERE id=2;  -- 锁住 id=2
                                        UPDATE user SET name='B' WHERE id=1;  -- 等待 A 的 id=1
UPDATE user SET name='A' WHERE id=2;  -- 等待 B 的 id=2
                                        ────────────────────
                                        死锁!

InnoDB 死锁处理

InnoDB 自动检测死锁,选择回滚最小事务(undo log 少)。

检测到死锁
    ↓
选择回滚代价最小的事务
    ↓
回滚该事务,释放锁
    ↓
其他事务继续执行

如何避免死锁?

-- 1. 固定顺序访问资源
UPDATE user SET ... WHERE id=1;  -- 先 id=1
UPDATE user SET ... WHERE id=2;  -- 再 id=2
-- 事务 B 也按同样顺序 → 不会死锁

-- 2. 减少锁持有时间
-- 不要在事务中做太多事情

-- 3. 使用低隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 4. 添加合理索引
-- 减少锁的范围

锁相关命令

-- 查看当前锁等待
SHOW ENGINE INNODB STATUS;

-- 查看锁信息
SELECT * FROM information_schema.INNODB_LOCKS;

-- 查看锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 查看事务
SELECT * FROM information_schema.INNODB_TRX;

-- 杀掉阻塞进程
KILL trx_mysql_thread_id;

面试高频问题

Q1:InnoDB 和 MyISAM 的锁有什么区别?

特性InnoDBMyISAM
锁粒度行锁 + 表锁表锁
事务支持支持不支持
锁类型共享/排他/意向共享/排他
死锁可能(自动检测)不可能
MVCC支持不支持
索引聚簇索引非聚簇

Q2:什么时候会锁全表?

-- 1. 不走索引
UPDATE user SET name='new' WHERE name='old';  -- name 没有索引

-- 2. 使用 LIKE 模糊匹配
SELECT * FROM user WHERE name LIKE '%张%' FOR UPDATE;  -- 可能锁全表

-- 3. 锁表操作
LOCK TABLES user WRITE;

Q3:RR 隔离级别如何避免幻读?

  1. 快照读:MVCC 保证每次快照读看到的数据一致
  2. 当前读:Next-Key Lock 锁住索引范围

Q4:如何排查死锁?

-- 1. 查看死锁日志
SHOW ENGINE INNODB STATUS;

-- 2. 查看最近死锁
SHOW FULL PROCESSLIST;

总结

锁分类:
├─ 按粒度:行锁、表锁、页面锁
├─ 按类型:共享锁(S)、排他锁(X)
└─ 按算法:Record Lock、Gap Lock、Next-Key Lock

InnoDB 锁特点:
├─ 行锁 + 意向锁
├─ 支持事务
├─ MVCC + Next-Key Lock
└─ 自动死锁检测

避免死锁:
├─ 固定顺序访问
├─ 减少锁持有时间
├─ 合理使用索引
└─ 降低隔离级别

乐观锁 vs 悲观锁:
├─ 悲观锁:先加锁后操作
└─ 乐观锁:先操作后检查(版本号/CAS)

评论区