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 会 全表扫描,扫描到的所有记录都会加锁(锁全表)。

这俩就会互相阻塞