1.如何用 MySQL 实现一个可重入的锁?
这句话 “如何用 MySQL 实现一个可重入的锁?” 是一道非常典型的面试题,考察你对 数据库事务 + 行锁机制 的理解。
🧩 一、先理解什么是「可重入锁」
👉 普通锁:同一个线程(或事务)如果已经拿到锁,再次尝试加同一把锁会被阻塞。 👉 可重入锁(Reentrant Lock):同一个线程可以“重复地”加锁,不会被自己卡住。 而是只要每加一次,就记录一次“重入计数”;解锁时每释放一次计数减 1,直到为 0 时才真正释放。
🧠 二、用 MySQL 怎么做?
在没有 Redis、ZooKeeper 的时候,也可以用 MySQL 来实现“分布式锁”,甚至是“可重入锁”。 核心思路是:
利用表记录锁的持有者和重入次数。 比如建一个锁表:
CREATE TABLE `lock_table` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`lock_name` VARCHAR(255) NOT NULL, -- 锁的名字
`holder_thread` VARCHAR(255), -- 当前持有锁的线程名
`reentry_count` INT DEFAULT 0 -- 重入次数
);🔒 三、加锁逻辑(lock)
1️⃣ 开启事务 2️⃣ 查询这把锁是否已经被别人持有
SELECT holder_thread, reentry_count
FROM lock_table
WHERE lock_name = ? FOR UPDATE;FOR UPDATE 保证查询到的行被锁住。
3️⃣ 分两种情况:
- 没有记录:说明没人拿锁 → 插入新锁记录
INSERT INTO lock_table(lock_name, holder_thread, reentry_count) VALUES(?, ?, 1); - 有记录,且是同一线程:说明是“可重入” → 计数 +1
UPDATE lock_table SET reentry_count = reentry_count + 1 WHERE lock_name = ?;
4️⃣ 提交事务 ✅
🔓 四、解锁逻辑(unlock)
1️⃣ 开启事务 2️⃣ 查询这把锁:
SELECT holder_thread, reentry_count
FROM lock_table
WHERE lock_name = ? FOR UPDATE;3️⃣ 如果当前线程正持有锁:
- 若
reentry_count > 1→ 只是重入层级减少UPDATE lock_table SET reentry_count = reentry_count - 1 WHERE lock_name = ?; - 若
reentry_count = 1→ 全部释放DELETE FROM lock_table WHERE lock_name = ?;
4️⃣ 提交事务 ✅
2.讲一下mysql里有哪些锁?
MySQL 有全局锁、表级锁(表锁、MDL、意向锁),以及 InnoDB 的行级锁(记录锁、间隙锁、Next-Key 锁)。行级锁配合 MVCC 解决并发读写问题,Next-Key 锁用于避免幻读。

| 范围 | 锁类型 | 特点 | |
|---|---|---|---|
| 🔒 全库 | 全局锁 | 整库只读 | **作用:**整个数据库进入只读状态。 **特点:**会阻塞所有写入和 DDL。 **使用场景:**全库逻辑备份(mysqldump)。 |
| 🔒 表级 | 表锁 | 手动加锁 | - READ:别人读可以,别人写不行 - WRITE:别人读写都不行 |
| MDL | 自动;保证 DDL 与 DML 冲突 | - 对表 增删改查 → 加 MDL 读锁 - 对表 结构修改(DDL) → 加 MDL 写锁 | |
| 意向锁 | 标记用;不阻塞 | 用来 快速判断表里是否有行锁。 | |
| 🔒 行级 | 记录锁 | 锁一行 | Record Lock锁住一行记录。 有两种:S 锁(共享锁):读锁 X 锁(排他锁):写锁 |
| InnoDB 才支持行锁,MyISAM 不支持。 | 间隙锁 | 锁范围,不含记录 | Gap Lock 锁住两个记录之间的“间隙”。 只在 可重复读(RR) 隔离级别出现。 |
| Next-Key 锁 | RR 下防幻读 | 是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。 |
3.数据库的表锁和行锁有什么作用?
数据库的锁本质是为了 保证并发一致性。
其中最常用的是:表锁 和 行锁,它们的作用完全不同。
表锁用于控制整表的并发,适合大范围操作;行锁用于精细粒度控制,支持高并发,适合 OLTP 场景。
| 对比项 | 表锁 | 行锁 |
|---|---|---|
| 锁范围 | 整张表 | 单行(或部分行) |
| 并发性 | 低 | 高 |
| 开销 | 小 | 大 |
| 冲突 | 多 | 少 |
| 是否支持 | MyISAM 支持;InnoDB 也支持 | 仅 InnoDB 支持 |
| 适用场景 | 批量操作、大查询 | 高频单行增删改 |
4.MySQL两个线程的update语句同时处理一条数据,会不会有阻塞?
会阻塞。两个事务同时更新同一行时,第二个事务会被阻塞,因为 UPDATE 会对记录加排他锁,排他锁之间互斥。
⭐ InnoDB 对 UPDATE 的目标记录会加 排他锁(X 锁)
-
第一个事务执行
UPDATE ... WHERE id = 1
→ 给主键 id=1 这一行加 行级排他锁(Record X Lock) -
第二个事务也想更新 id=1
→ 需要获取同一行的 X 锁
→ 但 X 锁互斥
→ 因此只能等待前一个事务释放锁(提交或回滚)
这就是 写写冲突。
5.两条update语句处理一张表的不同的主键范围的记录,一个<10,一个>15,会不会遇到阻塞?底层是为什么的
因为 两个 UPDATE 的条件范围完全不重叠,InnoDB 加的是 行级锁(Record Lock + Gap Lock),每个事务只会锁住自己命中的记录(或间隙),两个范围没有交集 → 不会冲突、不会阻塞。
⚠️ 但注意:如果 WHERE 条件没有用到索引 → 会阻塞!
UPDATE t SET ... WHERE age < 10; -- age 无索引 UPDATE t SET … WHERE age > 15;`
因为无法通过索引定位,MySQL 会 全表扫描,扫描到的所有记录都会加锁(锁全表)。
→ 这俩就会互相阻塞。