什么是死锁?下面本篇文章带大家了解一下mysql死锁。聊聊mysql出现死锁的必要条件,如何解决死锁问题?希望对大家有所帮助。

死锁指的是在两个或两个以上不同的进程或线程中,由于存在共同资源的竞争或进程(或线程)间的通讯而导致各个线程间相互挂起等待,如果没有外力作用,最终会引发整个系统崩溃。
资源独占条件
指多个事务在竞争同一个资源时存在互斥性,即在一段时间内某资源只由一个事务占用,也可叫独占资源(如行锁)。
请求和保持条件
指在一个事务a中已经获得锁A,但又提出了新的锁B请求,而该锁B已被其它事务b占有,此时该事务a则会阻塞,但又对自己已获得的锁A保持不放。
不剥夺条件
指一个事务a中已经获得锁A,在未提交之前,不能被剥夺,只能在使用完后提交事务再自己释放。
相互获取锁条件
指在发生死锁时,必然存在一个相互获取锁过程,即持有锁A的事务a在获取锁B的同时,持有锁B的事务b也在获取锁A,最终导致相互获取而各个事务都阻塞。
假设存在一个转账情景,A账户给B账户转账50元的同时,B账户也给A账户转账30元,那么在这过程中是否会存在死锁情况呢?
3.1 建表语句
CREATE TABLE `account` ( `id` int(11) NOT NULL COMMENT '主键', `user_id` varchar(56) NOT NULL COMMENT '用户id', `balance` float(10,2) DEFAULT NULL COMMENT '余额', PRIMARY KEY (`id`), UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='账户余额表';
3.2 初始化相关数据
INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (1, 'A', 80.00); INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (2, 'B', 60.00);

3.3 正常转账过程
在说死锁问题之前,咱们先来看看正常的转账过程。
正常情况下,A用户给B用户转账50元,可在一个事务内完成,需要先获取A用户的余额和B用户的余额,因为之后需要修改这两条数据,所以需要通过写锁(for UPDATE)锁住他们,防止其他事务更改导致我们的更改丢失而引起脏数据。
相关sql如下:
开启事务之前需要先把mysql的自动提交关闭
set autocommit=0; # 查看事务自动提交状态状态
show VARIABLES like 'autocommit';
# 转账sql START TRANSACTION; # 获取A 的余额并存入A_balance变量:80 SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE; # 获取B 的余额并存入B_balance变量:60 SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE; # 修改A 的余额 UPDATE account set balance = @A_balance - 50 where user_id = 'A'; # 修改B 的余额 UPDATE account set balance = @B_balance + 50 where user_id = 'B'; COMMIT;
执行后的结果:

可以看到数据更新都是正常的情况
3.4 死锁转账过程
初始化的余额为:

假设在高并发情况下存在这种场景,A用户给B用户转账50元的同时,B用户也给A用户转账30元。
那么我们的j*a程序操作的过程和时间线如下:
1、A用户给B用户转账50元,需在程序中开启事务1来执行sql,并获取A的余额同时锁住A这条数据。
一览妙笔
自媒体、编剧、营销人员写作工具
50
查看详情
# 事务1 set autocommit=0; START TRANSACTION; # 获取A 的余额并存入A_balance变量:80 SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;
2、B用户给A用户转账30元,需在程序中开启事务2来执行sql,并获取B的余额同时锁住B这条数据。
# 事务2 set autocommit=0; START TRANSACTION; # 获取A 的余额并存入A_balance变量:60 SELECT user_id,@A_balance:=balance from account where user_id = 'B' for UPDATE;
3、在事务1中执行剩下的sql
# 获取B 的余额并存入B_balance变量:60 SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE; # 修改A 的余额 UPDATE account set balance = @A_balance - 50 where user_id = 'A'; # 修改B 的余额 UPDATE account set balance = @B_balance + 50 where user_id = 'B'; COMMIT;

可以看到,在事务1中获取B数据的写锁时出现了超时情况。为什么会这样呢?主要是因为我们在步骤2的时候已经在事务2中获取到B数据的写锁了,那么在事务2提交或回滚前事务1永远都拿不到B数据的写锁。
4、在事务2中执行剩下的sql
# 获取A 的余额并存入B_balance变量:60 SELECT user_id,@B_balance:=balance from account where user_id = 'A' for UPDATE; # 修改B 的余额 UPDATE account set balance = @A_balance - 30 where user_id = 'B'; # 修改A 的余额 UPDATE account set balance = @B_balance + 30 where user_id = 'A'; COMMIT;

同理可得,在事务2中获取A数据的写锁时也出现了超时情况。因为步骤1的时候已经在事务1中获取到A数据的写锁了,那么在事务1提交或回滚前事务2永远都拿不到A数据的写锁。
5、 为什么会出现这种情况呢?
主要是因为事务1和事务2存在相互等待获取锁的过程,导致两个事务都挂起阻塞,最终抛出获取锁超时的异常。

3.5 死锁导致的问题
众所周知,数据库的连接资源是很珍贵的,如果一个连接因为事务阻塞长时间不释放,那么后面新的请求要执行的sql也会排队等待,越积越多,最终会拖垮整个应用。一旦你的应用部署在微服务体系中而又没有做熔断处理,由于整个链路被阻断,那么就会引发雪崩效应,导致很严重的生产事故。
要想解决死锁问题,我们可以从死锁的四个必要条件入手。
由于资源独占条件和不剥夺条件是锁本质的功能体现,无法修改,所以咱们从另外两个条件尝试去解决。
4.1 打破请求和保持条件
根据上面定义可知,出现这个情况是因为事务1和事务2同时去竞争锁A和锁B,那么我们是否可以保证锁A和锁B一次只能被一个事务竞争和持有呢?
答案是肯定可以的。下面咱们通过伪代码来看看:
/**
* 事务1入参(A, B)
* 事务2入参(B, A)
**/
public void transferAccounts(String userFrom, String userTo) {
// 获取分布式锁
Lock lock = Redisson.getLock();
// 开启事务
JDBC.excute("START TRANSACTION;");
// 执行转账sql
JDBC.excute("# 获取A 的余额并存入A_balance变量:80\n" +
"SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
"# 获取B 的余额并存入B_balance变量:60\n" +
"SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;\n" +
"\n" +
"# 修改A 的余额\n" +
"UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';\n" +
"# 修改B 的余额\n" +
"UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';\n");
// 提交事务
JDBC.excute("COMMIT;");
// 释放锁
lock.unLock();
}上面的伪代码显而易见可以解决死锁问题,因为所有的事务都是通过分布式锁来串行执行的。
那么这样就真的万事大吉了吗?
在小流量情况下看起来是没问题的,但是在高并发场景下这里将成为整个服务的性能瓶颈,因为即使你部署了再多的机器,但由于分布式锁的原因,你的业务也只能串行进行,服务性能并不因为集群部署而提高并发量,完全无法满足分布式业务下快、准、稳的要求,所以咱们不妨换种方式来看看怎么解决死锁问题。
4.2 打破相互获取锁条件(推荐)
要打破这个条件其实也很简单,那就是事务再获取锁的过程中保证顺序获取即可,也就是锁A始终在锁B之前获取。
我们来看看之前的伪代码怎么优化?
/**
* 事务1入参(A, B)
* 事务2入参(B, A)
**/
public void transferAccounts(String userFrom, String userTo) {
// 对用户A和B进行排序,让userFrom始终为用户A,userTo始终为用户B
if (userFrom.hashCode() > userTo.hashCode()) {
String tmp = userFrom;
userFrom = userTo;
userTo = tmp;
}
// 开启事务
JDBC.excute("START TRANSACTION;");
// 执行转账sql
JDBC.excute("# 获取A 的余额并存入A_balance变量:80\n" +
"SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
"# 获取B 的余额并存入B_balance变量:60\n" +
"SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;\n" +
"\n" +
"# 修改A 的余额\n" +
"UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';\n" +
"# 修改B 的余额\n" +
"UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';\n");
// 提交事务
JDBC.excute("COMMIT;");
}假设事务1的入参为(A, B),事务2入参为(B, A),由于我们对两个用户参数进行了排序,所以在事务1中需要先获取锁A在获取锁B,事务2也是一样要先获取锁A在获取锁B,两个事务都是顺序获取锁,所以也就打破了相互获取锁的条件,最终完美解决死锁问题。
因为mysql在互联网中的大量使用,所以死锁问题还是经常会被问到,希望兄弟们能掌握这方面的知识,提高自己的竞争力。
【相关推荐:mysql视频教程】
以上就是什么是死锁?聊聊对MySQL死锁的理解的详细内容,更多请关注其它相关文章!
# 锁住
# 河南专业seo优化
# 金华网站优化常识培训
# 线上乐高教育推广营销
# 沁源网络推广营销招聘网
# 青岛店铺营销推广渠道
# 全民健身设施网站建设规范
# 安溪产品软文营销推广
# seo代码详解
# 静安区科技营销推广中心
# zhanzhanggongju seo
# 死锁
# 是因为
# 客户端
# 必要条件
# 来看看
# 数据库中
# 镜像
# 执行时间
# 都是
# mysql
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
漫蛙manwa2网页版书签同步链接_漫蛙manwa多设备登录入口
《下一站江湖2》心法融合技巧
mysql镜像配置如何设置用户权限组_mysql镜像配置用户组与权限分级管理方法
c++如何使用std::thread::join和detach_c++线程生命周期管理
附近酒吧怎么找?
响应式设计中动态背景颜色条的实现指南
2025考研成绩查询时间入口分享
Flask 应用中图片动态更新与上传:实现客户端定时刷新与服务器端文件管理
51漫画网实时入口 51漫画网页版官方免费漫画入口
实时数据流中高效查找最小值与最大值
J*aScript模拟悬停与点击:自动化网页动态元素交互指南
Microsoft Edge网页字体太淡看不清怎么办_Microsoft Edge字体渲染优化技巧
深入理解随机递归函数的确定性:内部节点、叶节点与时间复杂度分析
解决J*aScript动态图片上传中ID重复问题:在同一页面显示多张独立图片
139邮箱登录入口官网 139邮箱登录入口官网网址
Excel如何快速找到并断开外部数据源链接_Excel外部数据源断开方法
铁路12306入口 铁路12306官网版入口登录网址
pubmed数据库官方主页_pubmed学术论文查找官网直达
C++如何实现矩阵乘法_C++二维数组矩阵运算代码示例
如何在解析前预检查XML文件的完整性? 比如检查文件大小或特定结束标签
QQ邮箱官方登录页_腾讯出品安全稳定的邮箱服务
热血江湖归来医师加点攻略
Three.js中动态更换3D模型纹理的教程
J*aScript中高效处理用户输入:从Keyup事件到表单提交的优化实践
《爱南宁》认证电动车方法
微信朋友圈怎么设置三天可见 微信朋友圈设置指定天数可见步骤【教程】
如何在CSS中使用伪类选择器_hover实现悬停效果
青橙手机语音助手怎么唤醒_青橙手机语音助手设置与唤醒方法
C++ bind函数使用教程_C++参数绑定与函数适配器的应用
Final Cut Pro视频加EQ教程
win11如何开启单声道音频 Win11为听障用户合并左右声道【辅助】
《淘宝联盟》推广自己的店铺方法
c++如何实现一个简单的RPC框架_c++远程过程调用原理与实践
sublime如何处理超大文件不卡顿 _sublime打开大日志文件技巧
雨课堂官网在线登录 网页版雨课堂登录链接
微信如何设置字体大小_微信字体设置的阅读舒适
谷歌浏览器官方镜像获取方法_谷歌浏览器网页版入口极速直达
厨房地面防滑垫的油污怎么洗? 机洗和手洗防滑垫的注意事项
《花瓣》创建专辑方法
sublime怎么在文件中显示代码结构大纲_sublime符号列表功能
Windows自带的便笺数据如何备份_防止数据丢失的便利贴迁移教程【干货】
在Flask应用中安全高效地更新SQLAlchemy用户数据
C++如何将字符串转换为大写或小写_C++ transform函数的使用技巧
电子白板帮助菜单使用指南
猫眼电影app怎么查询电影院的营业时间_猫眼电影影院营业时间查询教程
苹果电脑如何快速查看电池状态 苹果电脑电池信息快捷方法
海棠阅读网页版_进入海棠网页版在线阅读中心
阿里云共享相册入口在哪
包子漫画官网链接官方地址 包子漫画在线观看官网首页入口
优化CSS动画与J*aScript定时器协同:构建稳定Toast提示
2022-08-24
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。