当前位置: 首页 > news >正文

详细解说基于mysql分布式锁的三种实现方式

更多备考资料-> 免费刷题网站

基于MySQL实现分布式锁主要有以下三种核心方法,每种方法均有其实现原理和适用场景:

一、基于唯一索引/约束的实现

定义

通过数据库唯一索引的排他性实现锁竞争。创建包含唯一字段(如lock_name)的表,加锁时尝试插入记录,成功则获取锁,失败则自旋重试。释放锁时删除对应记录

特点‌:

实现简单,依赖INSERT操作的原子性
需处理锁超时(通过expire_time字段和定时任务清理过期锁)
缺点包括非阻塞性、不可重入,且数据库单点故障会影响锁可用性。

实现步骤

‌步骤1:创建锁表

CREATE TABLE `distributed_lock` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`lock_key` varchar(64) NOT NULL COMMENT '锁标识(需加唯一索引)',`holder_id` varchar(128) NOT NULL COMMENT '锁持有者标识(如IP+线程ID)',`expire_time` datetime NOT NULL COMMENT '锁过期时间',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `uk_lock_key` (`lock_key`)  -- 关键:唯一索引保证互斥
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

‌步骤2:加锁逻辑(Java示例)

public boolean tryLock(String lockKey, String holderId, int expireSeconds) {Connection conn = null;try {conn = dataSource.getConnection();conn.setAutoCommit(false);// 1. 尝试插入锁记录String sql = "INSERT INTO distributed_lock (lock_key, holder_id, expire_time) " +"VALUES (?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND))";try (PreparedStatement ps = conn.prepareStatement(sql)) {ps.setString(1, lockKey);ps.setString(2, holderId);ps.setInt(3, expireSeconds);int affectedRows = ps.executeUpdate();if (affectedRows > 0) {conn.commit();return true;  // 加锁成功}}// 2. 插入失败时检查锁是否过期sql = "SELECT id FROM distributed_lock WHERE lock_key=? AND expire_time < NOW()";try (PreparedStatement ps = conn.prepareStatement(sql)) {ps.setString(1, lockKey);ResultSet rs = ps.executeQuery();if (rs.next()) {// 3. 锁已过期,尝试抢占sql = "UPDATE distributed_lock SET holder_id=?, expire_time=DATE_ADD(NOW(), INTERVAL ? SECOND) " +"WHERE lock_key=? AND expire_time < NOW()";try (PreparedStatement updatePs = conn.prepareStatement(sql)) {updatePs.setString(1, holderId);updatePs.setInt(2, expireSeconds);updatePs.setString(3, lockKey);if (updatePs.executeUpdate() > 0) {conn.commit();return true;  // 抢占成功}}}}conn.rollback();return false;  // 加锁失败} catch (SQLException e) {if (conn != null) try { conn.rollback(); } catch (SQLException ignored) {}throw new RuntimeException("加锁异常", e);} finally {if (conn != null) try { conn.close(); } catch (SQLException ignored) {}}
}

‌步骤3:解锁逻辑

public void unlock(String lockKey, String holderId) {String sql = "DELETE FROM distributed_lock WHERE lock_key=? AND holder_id=?";try (Connection conn = dataSource.getConnection();PreparedStatement ps = conn.prepareStatement(sql)) {ps.setString(1, lockKey);ps.setString(2, holderId);ps.executeUpdate();} catch (SQLException e) {throw new RuntimeException("解锁异常", e);}
}

‌关键点‌
‌锁续期‌:需后台线程定期执行UPDATE distributed_lock SET expire_time=新时间 WHERE lock_key=? AND holder_id=?。
‌死锁处理‌:通过expire_time自动失效锁,避免死锁。

二、基于悲观锁(SELECT FOR UPDATE)

利用InnoDB行锁机制,通过SELECT … FOR UPDATE锁定特定记录。事务提交或回滚时自动释放锁
‌实现步骤‌:
预存锁记录到表(如lock表)
执行SELECT lock_name FROM lock WHERE lock_name=‘key’ FOR UPDATE获取锁
需手动管理事务提交以释放锁
‌优化点‌:
结合线程标识支持可重入
避免锁表需确保WHERE条件使用索引

‌步骤1:初始化锁记录

INSERT INTO `pessimistic_lock` (`lock_key`, `version`) VALUES ('order_lock', 1);

‌步骤2:加锁逻辑

public boolean tryLock(String lockKey, long timeoutMs) {Connection conn = null;try {conn = dataSource.getConnection();conn.setAutoCommit(false);// 1. 设置事务超时(避免长时间阻塞)try (Statement stmt = conn.createStatement()) {stmt.execute("SET SESSION innodb_lock_wait_timeout=" + (timeoutMs / 1000));}// 2. 尝试获取行锁String sql = "SELECT * FROM pessimistic_lock WHERE lock_key=? FOR UPDATE";try (PreparedStatement ps = conn.prepareStatement(sql)) {ps.setString(1, lockKey);ResultSet rs = ps.executeQuery();if (rs.next()) {return true;  // 加锁成功(事务提交前持续持有锁)}}return false;} catch (SQLException e) {if (e.getErrorCode() == 1205) {  // MySQL锁等待超时错误码return false;}throw new RuntimeException("加锁异常", e);}
}

‌步骤3:解锁逻辑

public void unlock(Connection lockedConn) {try {lockedConn.commit();  // 提交事务即释放锁} catch (SQLException e) {throw new RuntimeException("解锁异常", e);} finally {if (lockedConn != null) try { lockedConn.close(); } catch (SQLException ignored) {}}
}

‌关键点‌
**‌事务管理‌:**必须确保加锁和解锁在同一个事务中。
‌可重入支持‌:可在表中增加holder_id字段,校验当前线程是否已持有锁。

三、基于乐观锁(版本号机制)

通过版本号或时间戳实现CAS(Compare-And-Swap)。表中增加version字段,更新时校验版本号,匹配则更新成功并获取锁
‌流程‌:
查询当前版本号:SELECT version FROM lock WHERE resource_id=‘X’
更新时带版本条件:UPDATE lock SET version=version+1 WHERE resource_id=‘X’ AND version=old_version
返回影响行数判断加锁结果
‌适用场景‌:低冲突环境,如库存扣减

‌步骤1:建表

CREATE TABLE `optimistic_lock` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`resource_id` varchar(64) NOT NULL COMMENT '业务资源ID',`version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',PRIMARY KEY (`id`),UNIQUE KEY `uk_resource_id` (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

‌步骤2:加锁逻辑

public boolean tryLock(String resourceId) {String selectSql = "SELECT version FROM optimistic_lock WHERE resource_id=?";String updateSql = "UPDATE optimistic_lock SET version=version+1 WHERE resource_id=? AND version=?";try (Connection conn = dataSource.getConnection()) {// 1. 查询当前版本号int currentVersion;try (PreparedStatement ps = conn.prepareStatement(selectSql)) {ps.setString(1, resourceId);ResultSet rs = ps.executeQuery();if (!rs.next()) {throw new RuntimeException("资源不存在");}currentVersion = rs.getInt("version");}// 2. 尝试更新版本号try (PreparedStatement ps = conn.prepareStatement(updateSql)) {ps.setString(1, resourceId);ps.setInt(2, currentVersion);return ps.executeUpdate() > 0;  // 影响行数>0表示加锁成功}} catch (SQLException e) {throw new RuntimeException("乐观锁异常", e);}
}

‌关键点‌
‌自旋重试‌:需在业务代码中循环调用tryLock直到成功或超时。
无阻塞‌:失败后立即返回,适合低冲突场景。

‌总结对比

在这里插入图片描述
‌选择建议‌:

需要强一致性 → 悲观锁
高并发且冲突少 → 乐观锁
快速实现且容忍一定缺陷 → 唯一索引

更多备考资料-> 免费刷题网站

http://www.dtcms.com/a/427362.html

相关文章:

  • 外贸网站设计注意事项网站繁体和中文这么做
  • AdGuard解锁订阅版高级版 安卓广告拦截器APP v4.11.63 / 4.13.7 Nightly MOD
  • 网站建设免费书江宁网站制作
  • claude code + claude code router 接入魔搭、openrouter等
  • 图观 流渲染场景服务器
  • Android Studio 代码混淆核心解释
  • 雨晨WIN11PE网络版VIP资源国庆限时开放
  • 网站改版Excel怎么做泰安抖音seo
  • Websocket+Redis实现微服务消息实时同步
  • 仪器仪表第四节课学习笔记
  • Java 黑马程序员学习笔记(进阶篇15)
  • 【开题答辩过程】以《基于SpringBoot+Vue+uni-app的智慧校园服务系统的设计与实现》为例,不会开题答辩的可以进来看看
  • 做二手电脑的网站宣城网站建设 有限公司
  • 没有服务器 怎么做网站建设企业高端网站
  • 极简时钟APP(手机全能计时工具) 极简版
  • 华为光模块命名规则
  • 做企业网站用什么cms好易语言怎么做网页网站
  • 域名做网站北京 网站 公司
  • 深入浅出 Redis:从核心原理到运维实战指南一
  • 自定义含工具包`Ubuntu22.04.5.iso`镜像
  • Day 29 - 密码管理器开发 - Python学习笔记
  • Docker镜像结构全解析
  • ubuntu 22.04安装CUDA 13.0
  • 鸿蒙NEXT Wi-Fi扫描开发指南:从基础到实战
  • wordpress做的学校网站北京网页设计公司
  • 精读 C++20 设计模式:行为型设计模式——观察者模式
  • 广州营销型网站建设旅游网站内容规划特点
  • 【mdBook】6 在持续集成中运行 mdbook
  • Jenkins安装并与GitLab集成,实现dev、qa、uat、prod多分支持续集成的详细步骤
  • k8s部署前后分离架构微服务——跨域和缓存问题