MySQL做分布式锁
分布式锁mysql实现方式
方式1:唯一索引
- 创建锁表,内部存在字段表示资源名及资源描述,同一资源名使用数据库唯一性限制。
- 多个进程同时往数据库锁表中写入对某个资源的占有记录,当某个进程成功写入时则表示其获取锁成功
- 其他进程由于资源字段唯一性限制插入失败陷入自旋并且失败重试。
- 当执行完业务后持有该锁的进程则删除该表内的记录,此时回到步骤一。

表数据
create table `database_lock`(`id` BIGINT NOT NULL AUTO_INCREMENT,`resource` INT NOT NULL COMMENT '锁资源',`description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',PRIMARY KEY (`id`),UNIQUE KEY `resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/distribute_lock?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
user=root
password=123456
- PropertiesReader
@Slf4j
public class PropertiesReader {// Properties缓存文件private static final Map<String, Properties> propertiesCache = new HashMap<String, Properties>();public static Properties getProperties(String propertiesName) throws IOException {if (propertiesCache.containsKey(propertiesName)) {return propertiesCache.get(propertiesName);}loadProperties(propertiesName);return propertiesCache.get(propertiesName);}private synchronized static void loadProperties(String propertiesName) throws IOException {FileReader fileReader = null;try {// 创建Properties集合类Properties pro = new Properties();// 获取src路径下的文件--->ClassLoader类加载器ClassLoader classLoader = PropertiesReader.class.getClassLoader();URL resource = classLoader.getResource(propertiesName);// 获取配置路径String path = resource.getPath();// 读取文件fileReader = new FileReader(path);// 加载文件pro.load(fileReader);// 初始化propertiesCache.put(propertiesName, pro);} catch (IOException e) {log.error("读取Properties文件失败,Properties名为:" + propertiesName);throw e;} finally {try {if (fileReader != null) {fileReader.close();}} catch (IOException e) {log.error("fileReader关闭失败!", e);}}}
}
- JDBCUtils
@Slf4j
public class JDBCUtils {private static String url;private static String user;private static String password;static {//读取文件,获取值try {Properties properties = PropertiesReader.getProperties("db.properties");url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");String driver = properties.getProperty("driver");//4.注册驱动Class.forName(driver);} catch (IOException | ClassNotFoundException e) {log.error("初始化jdbc连接失败!", e);}}/*** 获取连接* @return 连接对象*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, user, password);}/*** 释放资源* @param rs* @param st* @param conn*/public static void close(ResultSet rs, Statement st, Connection conn) {if (rs != null) {try {rs.close();} catch (SQLException e) {e.printStackTrace();}}if (st != null) {try {st.close();} catch (SQLException e) {e.printStackTrace();}}if (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}
}
数据库操作类
/*** MySQL 锁操作类(加锁+释放锁)*/
@Slf4j
public class MySQLDistributedLockService {private static Connection connection;private static Statement statement;private static ResultSet resultSet;static{try {connection = JDBCUtils.getConnection();statement = connection.createStatement();resultSet = null;} catch (SQLException e) {log.error("数据库连接失败!");}}/*** 锁表 - 获取锁* @param resource 资源* @param description 锁描述* @return 是否操作成功*/public static boolean tryLock(int resource,String description){String sql = "insert into database_lock (resource,description) values (" + resource + ", '" + description + "');";//获取数据库连接try {int stat = statement.executeUpdate(sql);return stat == 1;} catch (SQLException e) {return false;}}/*** 锁表-释放锁* @return*/public static boolean releaseLock(int resource) throws SQLException {String sql = "delete from database_lock where resource = " + resource;//获取数据库连接int stat = statement.executeUpdate(sql);return stat == 1;}/*** 关闭连接*/public static void close(){log.info("当前线程: " + ManagementFactory.getRuntimeMXBean().getName().split("@")[0] +",关闭了数据库连接!");JDBCUtils.close(resultSet,statement,connection);}
}
LockTable
/*** mysql分布式锁* 执行流程: 多进程抢占数据库某个资源,然后执行业务,执行完释放资源* 锁机制: 单一进程获取锁时,则其他进程提交失败*/
@Slf4j
public class LockTable extends Thread {@Overridepublic void run() {super.run();//获取Java虚拟机的进程IDString pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];try{while(true){log.info("当前进程PID:" + pid + ",尝试获取锁资源!");if(MySQLDistributedLockService.tryLock(1,"测试锁")){log.info("当前进程PID:" + pid + ",获取锁资源成功!");//sleep模拟业务处理过程log.info("开始处理业务!");Thread.sleep(10*1000);log.info("业务处理完成!");MySQLDistributedLockService.releaseLock(1);log.info("当前进程PID: " + pid + ",释放了锁资源!");break;}else{log.info("当前进程PID: " + pid + ",获取锁资源失败!");Thread.sleep(2000);}}}catch (Exception e){log.error("抢占锁发生错误!",e);}finally {MySQLDistributedLockService.close();}}// 程序入口public static void main(String[] args) {new LockTable().start();}
}
测试
运行时开启并行执行选项,每次运行三个或三个以上进程. Allow parallel run 运行并行执行


注意事项:
- 该锁为非阻塞的
- 当某进程持有锁并且挂死时候会造成资源一直不释放的情况,造成死锁,因此需要维护一个定时清理任务去清理持有过久的锁
- 要注意数据库的单点问题,最好设置备库,进一步提高可靠性
- 该锁为非可重入锁,如果要设置成可重入锁需要添加数据库字段记录持有该锁的设备信息以及加锁次数
方式二:基于乐观锁
- 每次执行业务前首先进行数据库查询,查询当前的需要修改的资源值(或版本号)。
- 进行资源的修改操作,并且修改前进行资源(或版本号)的比对操作,比较此时数据库中的值是否和上一步查询结果相同。
- 查询结果相同则修改对应资源值,不同则回到第一步。

例子:数据库中设定某商品基本信息(名为外科口罩,数量为10),多进程对该商品进行抢购,当商品数量为0时结束抢购。

代码实现
/*** 乐观锁-获取资源* @param id 资源ID* @return result*/public static ResultSet getGoodCount(int id) throws SQLException {String sql = "select * from database_lock_2 where id = " + id;//查询数据resultSet = statement.executeQuery(sql);return resultSet;}/*** 乐观锁-修改资源* @param id 资源ID* @param goodCount 资源* @return 修改状态*/public static boolean setGoodCount(int id, int goodCount) throws SQLException {String sql = "update database_lock_2 set good_count = good_count - 1 where id =" + id +" and good_count = " + goodCount;int stat = statement.executeUpdate(sql);return stat == 1;}/*** 乐观锁-开启事务自动提交*/public static void AutoCommit(){try {connection.setAutoCommit(true);} catch (SQLException e) {log.error("开启自动提交!",e);}}
OptimisticLock测试类
/*** mysql分布式锁-乐观锁* 执行流程: 多进程抢购同一商品,每次抢购成功商品数量-1,商品数据量为0时退出* 锁机制: 单一进程获取锁时,则其他进程提交失败*/
@Slf4j
public class OptimisticLock extends Thread{@Overridepublic void run() {super.run();String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];ResultSet resultSet = null;String goodName = null;int goodCount = 0;try {while(true){log.info("当前线程:" + pid + ",开始抢购商品!");//获取当前商品信息resultSet = MySQLDistributedLockService.getGoodCount(1);while (resultSet.next()){goodName = resultSet.getString("good_name");goodCount = resultSet.getInt("good_count");}log.info("获取库存成功,当前商品名为:" + goodName + ",当前库存剩余量为:" + goodCount);//模拟执行业务操作Thread.sleep(2*3000);if(0 == goodCount){log.info("抢购失败,当前库存为0! ");break;}//修改库存信息,库存量-1if(MySQLDistributedLockService.setGoodCount(1,goodCount)){log.info("当前线程:" + pid + " 抢购商品:" + goodName + "成功,剩余库存为:" + (goodCount -1));//模拟延迟,防止锁每次被同一进程获取Thread.sleep(2 * 1000);}else{log.error("抢购商品:" + goodName +"失败,商品数量已被修改");}}}catch (Exception e){log.error("抢购商品发生错误!",e);}finally {if(resultSet != null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();log.error("关闭Result失败!" , e);}}MySQLDistributedLockService.close();}}public static void main(String[] args) {new OptimisticLock().start();}
}
代码测试
开启三个进程,查看执行情况



注意事项:
- 该锁为非阻塞的
- 该锁对于业务具有侵入式,如果设置版本号校验则增加的额外的字段,增加了数据库冗余
- 当并发量过高时会有大量请求访问数据库的某行记录,对数据库造成很大的写压力
- 因此乐观锁适用于并发量不高,并且写操作不频繁的场景
方式三:悲观锁实现方式(利用事务加上行/表锁)

实现思路
- 关闭jdbc连接自动commit属性
- 每次执行业务前先使用查询语句后接for update表示锁定该行数据(注意查询条件如果未命中主键或索引,此时将会从行锁变为表锁)
- 执行业务流程修改表资源
- 执行commit操作
代码实现
MySQLDistributedLockService
/*** 悲观锁-获取资源* @param id 资源ID* @return result*/public static ResultSet getGoodCount2(int id) throws SQLException {String sql = "select * from database_lock_2 where id = " + id + "for update";//查询数据resultSet = statement.executeQuery(sql);return resultSet;}/*** 悲观锁-修改资源* @param id 资源ID* @return 修改状态*/public static boolean setGoodCount2(int id) throws SQLException {String sql = "update database_lock_2 set good_count = good_count - 1 where id =" + id;int stat = statement.executeUpdate(sql);return stat == 1;}/*** 悲观锁-关闭事务自动提交*/public static void closeAutoCommit(){try {connection.setAutoCommit(false);} catch (SQLException e) {log.error("关闭自动提交失败!",e);}}/*** 悲观锁-提交事务*/public static void commit(String pid,String goodName,int goodCount) throws SQLException {connection.commit();log.info("当前线程:" + pid + "抢购商品: " + goodName + "成功,剩余库存为:" + (goodCount-1));}/*** 悲观锁-回滚*/public static void rollBack() throws SQLException {connection.rollback();}
PessimisticLock
/*** mysql 分布式锁-悲观锁* 执行流程:多个进程抢占同一个商品,执行业务完毕则通过connection.commit() 释放锁* 锁机制:单一进程获取锁时,则其他进程将阻塞等待*/
@Slf4j
public class PessimisticLock extends Thread {@Overridepublic void run() {super.run();ResultSet resultSet = null;String goodName = null;int goodCount = 0;String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];//关闭自动提交MySQLDistributedLockService.closeAutoCommit();try{while(true){log.info("当前线程:" + pid + "");//获取库存resultSet = MySQLDistributedLockService.getGoodCount2(1);while (resultSet.next()) {goodName = resultSet.getString("good_name");goodCount = resultSet.getInt("good_count");}log.info("获取库存成功,当前商品名称为:" + goodName + ",当前库存剩余量为:" + goodCount);// 模拟执行业务事件Thread.sleep(2 * 1000);if (0 == goodCount) {log.info("抢购失败,当前库存为0!");break;}// 抢购商品if (MySQLDistributedLockService.setGoodCount2(1)) {// 模拟延时,防止锁每次被同一进程获取MySQLDistributedLockService.commit(pid, goodName, goodCount);Thread.sleep(2 * 1000);} else {log.error("抢购商品:" + goodName + "失败!");}}}catch (Exception e){//抢购失败log.error("抢购商品发生错误!",e);try {MySQLDistributedLockService.rollBack();} catch (SQLException ex) {log.error("回滚失败! ",e);}}finally {if(resultSet != null){try {resultSet.close();} catch (SQLException e) {log.error("Result关闭失败!",e);}}MySQLDistributedLockService.close();}}public static void main(String[] args) {new PessimisticLock().start();}
}
测试结果



注意事项:
- 该锁为阻塞锁
- 每次请求存在额外加锁的开销
- 在并发量很高的情况下会造成系统中存在大量阻塞的请求,影响系统的可用性
- 因此悲观锁适用于并发量不高,读操作不频繁的写场景
总结:
- 在实际使用中,由于受到性能以及稳定性约束,对于关系型数据库实现的分布式锁一般很少被用到。但是对于一些并发量不高、系统仅提供给内部人员使用的单一业务场景可以考虑使用关系型数据库分布式锁,因为其复杂度较低,可靠性也能够得到保证。
相关文章:
MySQL做分布式锁
分布式锁mysql实现方式 方式1:唯一索引 创建锁表,内部存在字段表示资源名及资源描述,同一资源名使用数据库唯一性限制。多个进程同时往数据库锁表中写入对某个资源的占有记录,当某个进程成功写入时则表示其获取锁成功其他进程由于…...
Python学习笔记:变量类型、字符串基本操作
1.注释 单行注释 # 单行注释 多行注释 """ 多行注释 """2.变量类型 # 基本变量类型 a 1 # integer b 1.5 # float c string # String d "string" # string e False # boolean # list\tuple\dictionar…...
JVM的组件、自动垃圾回收的工作原理、分代垃圾回收过程、可用的垃圾回收器类型
详细画的jvm模型图 https://www.processon.com/diagraming/64c8aa11c07d99075d934311 官方网址 https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html 相关概念 年轻代是所有新对象被分配和老化的地方。当年轻代填满时,这会导致m…...
【elementui】解决el-select组件失去焦点blur事件每次获取的是上一次选中值的问题
目录 【问题描述】 【问题摘要】 【分析问题】 【完整Test代码】 【封装自定义指令】 ↑↑↑↑↑↑↑↑↑↑↑↑ 不想看解决问题过程的可点击上方【封装自定义指令】目录直接跳转获取结果即可~~~ 【问题描述】 一位朋友遇到这么一个开发场景:在表格里面嵌入el-…...
通过了PMP考试,还有什么证书值得考?
自从7月24号公布了PMP成绩后,不少伙伴私信小编:通过PMP后还有哪些证书可以提升自己?一来是多份高含金量的证书可以多点竞争力,二来是加持自己的职业发展!今天小编就来给大家捋一捋! 一.NPDP认证 2016 年 4…...
页面技术基础-html
页面技术基础-html 环境准备:在JDBC中项目上完成代码定义 1. 新建一个 Module:filr->右键 -》Module -》Java-》next->名字(html_day1)->finish 2. 在 Moudle上右键-》第二个选项:add framework .. -> 选择JavaEE下第一个选项 Web Apllicat…...
/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28‘ not found
某项目中,我要给别人封装一个深度学习算法的SDK接口,运行在RK3588平台上,然后客户给我的交叉编译工具链是 然后我用他们给我的交叉编译工具链报下面的错误: aarch64-buildroot-linux-gnu-gcc --version /data/chw/aarch64/bin/cca…...
解决SVN或GIT忽略提交文件的问题
背景 使用IDEA 的SVN插件提交文件是总是会提交一些不需要提交的文件; 我们可以通过一些简单设置忽略这些文件。 git 在项目根目录新建文本文件,修改后缀为.gitignore 文件中添加内容 *.iml .project .gradle/ .idea/ target/ build/ .vscode/ .settings/ .facto…...
Django框架之路由用法
简介 路由简单的来说就是根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果,也就是 URL 与 Django 的视图建立映射关系。 Django 路由在 urls.py 配置,urls.py 中的每一条配置对应相应的处理方法。 Django 不同版本 urls.py 配…...
回文链表 LeetCode热题100
题目 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。 思路 利用快慢指针找到链表中间节点,反转后半段链表。前半段从头节点开始与后半段比较。 注意当链表节点…...
如何在群晖NAS中使用cpolar内网穿透
如何在群晖nas中使用cpolar内网穿透 文章目录 如何在群晖nas中使用cpolar内网穿透 今天,我们来为大家介绍,如何在群晖系统中,使用图形化界面的cpolar。 cpolar经过图形化改造后,使用方法已经简便了很多,基本与其他应用…...
无头单向不循环链表和带头双向循环链表的创建
Lei宝啊:个人主页 愿所有美好不期而遇 前言: 接下来我们将会了解最基础的链表--->单链表 以及最方便也是最爽的链表--->带头双向循环链表。 若有看不懂之处,可画图或者借鉴这里:反转单链表,对于数据结构而言&am…...
超简单的fastapi链接websocket用例
main.py from typing import Listfrom fastapi import FastAPI, WebSocket, WebSocketDisconnectapp FastAPI()class ConnectionManager:def __init__(self):# 存放激活的ws连接对象self.active_connections: List[WebSocket] []async def connect(self, ws: WebSocket):# 等…...
MySQL详解
目录 一、MySQL 概述二、MySQL 安装和配置三、MySQL 基础语法四、MySQL 高级语法五、MySQL 性能优化六、MySQL 应用场景和实例七、MySQL 开发工具和插件八、MySQL 学习资源和社区 一、MySQL 概述 MySQL 是一种开源的关系型数据库管理系统,最初由瑞典的 MySQL AB 公…...
Vue [Day2]
指令修饰符 v-model.trim v-model.number 事件名.stop click.stop 事件名.prevent keyup.enter <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-w…...
【前端|Javascript第1篇】一文搞懂Javascript的基本语法
欢迎来到JavaScript的奇妙世界!作为前端开发的基石,JavaScript为网页增色不少,赋予了静态页面活力与交互性。如果你是一名前端小白,对编程一无所知,或者只是听说过JavaScript却从未涉足过,那么你来对了地方…...
【Linux命令200例】cp用于复制文件和目录(常用)
🏆作者简介,黑夜开发者,全栈领域新星创作者✌,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆本文已收录于专栏:Linux命令大全。 🏆本专栏我们会通过具体的系统的命令讲解加上鲜…...
C高级_第二讲_shell指令和shell脚本_递归练习
思维导图 递归实现,输入一个数,输出这个数的每一位 int funh(int num){if(0 num){return 0;}else{funh(num/10);printf("%d\n", num%10);} }int main(int argc, const char *argv[]) {puts("请输入一个数");int num 0;scanf(&quo…...
静态路由综合实验
实验拓扑如下: 实验要求如下: 【1】R6为isp,接口IP地址均为公有地址;该设备只能配置IP地址,之后不能再对其进行任何配置 【2】R1~R5为局域网,私有IP地址192.168.1.0/24,请合理分配 【3】所有路由器上环回…...
Spring核心IOC控制反转思想-----Spring框架
import org.junit.Test;public class TestPublic {Testpublic void Test(){//控制反转是一种思想,是为了提高程序扩展力降低耦合度,达到DIP(Dependency Inversion Principle依赖倒置)原则//其核心是将对象的创建权交出去,由第三方容器负责管理,将对象和对象之间的维护权交出去,…...
从Mixamo到Unity:构建角色动画控制系统的完整实践指南
1. 从Mixamo获取角色动画资源 Mixamo是Adobe旗下专注于3D角色动画的在线资源库,提供大量免费且高质量的动作捕捉数据。对于刚接触Unity动画系统的新手来说,这个平台能快速解决"如何让角色动起来"的核心问题。我第一次使用Mixamo时,…...
Flutter 入门第九课:本地存储实战(SharedPreferences + 文件 + SQLite)
这节课是 Flutter 实现数据本地持久化的核心,也是 APP 开发的必备能力 —— 解决「重启后数据丢失」的问题,实现登录状态保存、离线缓存、历史记录、本地配置等核心业务场景。我们会系统学习 Flutter 三大本地存储方案,按轻量→中等→重量级划…...
计算机毕业设计:Python农产品电商数据采集与价格预估平台 Flask框架 Spark 线性回归 数据分析 可视化 大数据 大模型(建议收藏)✅
1、项目介绍 技术栈 采用 Python 语言开发,基于 Flask 框架搭建后端服务,使用 Spark 技术进行大数据处理,通过 requests 爬虫从惠农网采集农产品数据,运用线性回归预测算法模型进行价格预测,前端结合 Echarts 可视化库…...
Cursor AI免费VIP破解方案:如何绕过试用限制持续使用Pro功能
Cursor AI免费VIP破解方案:如何绕过试用限制持续使用Pro功能 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reached yo…...
BK3633 Keil 工程中自动化构建与版本管理的进阶配置指南
1. 为什么需要自动化构建与版本管理 在嵌入式开发中,每次手动编译、打包、命名固件都是件费时费力的事情。特别是像BK3633这样的蓝牙芯片项目,往往需要同时维护Debug和Release两个版本。Debug版本用于开发调试,需要保留日志输出和调试信息&am…...
2026届学术党必备的六大降重复率方案解析与推荐
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 通过先进自然语言处理跟机器学习技术构建的人工智能论文工具,正一步步改变传统学…...
2026最权威的十大降重复率网站解析与推荐
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 日益普及的人工智能生成内容的背景之下, 将文本被识别成AI创作的比率予以降低这一…...
sd-webui-reactor终极指南:AI换脸从未如此简单高效
sd-webui-reactor终极指南:AI换脸从未如此简单高效 【免费下载链接】sd-webui-reactor 项目地址: https://gitcode.com/gh_mirrors/sd/sd-webui-reactor sd-webui-reactor是一款强大的Stable Diffusion插件,为用户提供快速、简单且功能丰富的AI换…...
零代码网页抓取神器:Web Scraper Chrome扩展完整指南
零代码网页抓取神器:Web Scraper Chrome扩展完整指南 【免费下载链接】web-scraper-chrome-extension Web data extraction tool implemented as chrome extension 项目地址: https://gitcode.com/gh_mirrors/we/web-scraper-chrome-extension 想要从任何网站…...
三步掌握百度网盘秒传链接:网页工具全平台极速转存指南
三步掌握百度网盘秒传链接:网页工具全平台极速转存指南 【免费下载链接】baidupan-rapidupload 百度网盘秒传链接转存/生成/转换 网页工具 (全平台可用) 项目地址: https://gitcode.com/gh_mirrors/bai/baidupan-rapidupload 还在为百度网盘资源分享的繁琐流…...
