it编程 > 数据库 > Mysql

MySQL实现可重入锁的实践指南

10人参与 2026-03-17 Mysql

在分布式系统和并发编程中,锁是保证数据一致性的关键工具。而基于 mysql 实现的可重入锁,不仅能满足跨进程的互斥需求,还能支持同一个线程多次获取锁而不阻塞。记录一下我对 mysql 实现可重入锁的思考:如何用 mysql 实现可重入锁?为什么实现过程中必须依赖事务?希望能解决你的一些疑惑。

一、先搞懂:什么是可重入锁?

可重入锁(也叫递归锁)的核心特性是:同一个线程可以多次获取同一把锁,不会因为自己持有锁而发生死锁。

举个例子:

在 java 里 reentrantlock 就是典型的可重入锁,而我们要做的,是用 mysql 模拟出类似的效果。

二、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
);

这张表的核心字段:

三、核心实现:加锁与解锁逻辑

1. 加锁流程

1. 开启事务
2. 执行 sql:
   select holder_thread, reentry_count 
   from lock_table 
   where lock_name = ? for update;
   - 若记录不存在:执行 insert into lock_table (lock_name, holder_thread, reentry_count) values (?, ?, 1)
   - 若记录存在且持有者是当前线程:执行 update lock_table set reentry_count = reentry_count + 1 where lock_name = ?
3. 提交事务

2. 解锁流程

1. 开启事务
2. 执行 sql:
   select holder_thread, reentry_count 
   from lock_table 
   where lock_name = ? for update;
   - 若记录存在、持有者是当前线程且重入次数 > 1:执行 update lock_table set reentry_count = reentry_count - 1 where lock_name = ?
   - 若记录存在、持有者是当前线程且重入次数 ≤ 1:执行 delete from lock_table where lock_name = ?
3. 提交事务

四、灵魂拷问:为什么必须加事务?

很多同学会疑惑:“我不加事务,直接执行 sql 不行吗?” 答案是:不行。事务是 mysql 可重入锁的灵魂,没有事务,锁的特性会直接失效。我们从三个维度拆解原因:

1. 控制锁的生命周期:避免锁提前释放

mysql innodb 默认 autocommit=1,单条 sql 执行完毕后会自动提交事务。

如果不加事务:

这会导致:

而事务的作用就是:在事务提交前,锁不会被释放。从查询锁状态到完成写入操作,整个过程中锁都被当前事务持有,保证了互斥性和可重入性。

2. 保证操作原子性:避免并发数据错乱

加锁 / 解锁的核心逻辑是「查询 → 判断 → 写入」,这三步必须是原子操作,否则在高并发场景下会出现数据错乱。

举个并发场景:

事务的原子性可以完美解决这个问题:

3. 确保可重入正确性:重入次数的安全更新

可重入锁的核心是维护 reentry_count 字段:

如果不加事务:

事务的隔离性保证了:

五、完整示例:

我们用伪代码把加锁和解锁流程串起来,更直观地感受事务的作用:

加锁伪代码

public boolean lock(string lockname, string threadname) {
    connection conn = getconnection();
    try {
        // 1. 开启事务
        conn.setautocommit(false);
        // 2. 查询锁状态(加排他锁)
        string querysql = "select holder_thread, reentry_count from lock_table where lock_name = ? for update";
        try (preparedstatement ps = conn.preparestatement(querysql)) {
            ps.setstring(1, lockname);
            resultset rs = ps.executequery();
            if (rs.next()) {
                string holder = rs.getstring("holder_thread");
                int count = rs.getint("reentry_count");
                if (threadname.equals(holder)) {
                    // 可重入:重入次数+1
                    string updatesql = "update lock_table set reentry_count = reentry_count + 1 where lock_name = ?";
                    try (preparedstatement updateps = conn.preparestatement(updatesql)) {
                        updateps.setstring(1, lockname);
                        updateps.executeupdate();
                    }
                } else {
                    // 被其他线程持有,获取锁失败
                    conn.rollback();
                    return false;
                }
            } else {
                // 锁不存在,直接加锁
                string insertsql = "insert into lock_table (lock_name, holder_thread, reentry_count) values (?, ?, 1)";
                try (preparedstatement insertps = conn.preparestatement(insertsql)) {
                    insertps.setstring(1, lockname);
                    insertps.setstring(2, threadname);
                    insertps.executeupdate();
                }
            }
        }
        // 3. 提交事务
        conn.commit();
        return true;
    } catch (exception e) {
        // 异常回滚
        conn.rollback();
        return false;
    } finally {
        conn.setautocommit(true);
        closeconnection(conn);
    }
}

解锁伪代码

public boolean unlock(string lockname, string threadname) {
    connection conn = getconnection();
    try {
        // 1. 开启事务
        conn.setautocommit(false);
        // 2. 查询锁状态
        string querysql = "select holder_thread, reentry_count from lock_table where lock_name = ? for update";
        try (preparedstatement ps = conn.preparestatement(querysql)) {
            ps.setstring(1, lockname);
            resultset rs = ps.executequery();
            if (rs.next()) {
                string holder = rs.getstring("holder_thread");
                int count = rs.getint("reentry_count");
                if (!threadname.equals(holder)) {
                    // 不是锁持有者,解锁失败
                    conn.rollback();
                    return false;
                }
                if (count > 1) {
                    // 重入次数>1,仅减1
                    string updatesql = "update lock_table set reentry_count = reentry_count - 1 where lock_name = ?";
                    try (preparedstatement updateps = conn.preparestatement(updatesql)) {
                        updateps.setstring(1, lockname);
                        updateps.executeupdate();
                    }
                } else {
                    // 重入次数=1,删除锁记录
                    string deletesql = "delete from lock_table where lock_name = ?";
                    try (preparedstatement deleteps = conn.preparestatement(deletesql)) {
                        deleteps.setstring(1, lockname);
                        deleteps.executeupdate();
                    }
                }
            } else {
                // 锁不存在,解锁失败
                conn.rollback();
                return false;
            }
        }
        // 3. 提交事务
        conn.commit();
        return true;
    } catch (exception e) {
        conn.rollback();
        return false;
    } finally {
        conn.setautocommit(true);
        closeconnection(conn);
    }
}

六、总结

用 mysql 实现可重入锁,本质是用数据库表存储锁状态,用事务保证锁的互斥性、原子性和生命周期。

事务的核心作用可以概括为三点:

  1. 锁生命周期管理:事务提交前,锁不会释放,保证当前线程持续持有锁。
  2. 原子性保障:将「查询 → 判断 → 写入」封装为原子操作,避免并发数据错乱。
  3. 可重入正确性:安全维护重入次数,确保同一个线程可以多次获取 / 释放锁。

这种实现方式虽然不如 redis 等分布式锁框架高效,但胜在简单可靠,适合对性能要求不高、需要强一致性的场景,也能帮我们更好地理解事务和锁的本质。

以上就是mysql实现可重入锁的实践指南的详细内容,更多关于mysql可重入锁实现的资料请关注代码网其它相关文章!

(0)

您想发表意见!!点此发布评论

推荐阅读

MYSQL连接不上本地服务器localhost的问题及解决

03-17

MySQL5.7配置Pseudo-GTID完整步骤

03-17

MYSQL中锁的分类与加锁方式小结

03-17

MySQL中的数据库约束用法及说明

03-17

mysql中explain的具体实现

03-17

MySQL 存储引擎InnoDB 架构与原理深度解析

03-17

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论