1、什么是死锁?
2、Mysql出现死锁的必要条件
资源独占条件
(资料图)
请求和保持条件
不剥夺条件
相互获取锁条件
3、 Mysql经典死锁案例
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 正常转账过程
开启事务之前需要先把mysql的自动提交关闭
set autocommit=0;# 查看事务自动提交状态状态
show VARIABLES like "autocommit";
# 转账sqlSTART TRANSACTION;# 获取A 的余额并存入A_balance变量:80SELECT user_id,@A_balance:=balance from account where user_id = "A" for UPDATE;# 获取B 的余额并存入B_balance变量:60SELECT 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 死锁转账过程
初始化的余额为:
那么我们的java程序操作的过程和时间线如下:
1、A用户给B用户转账50元,需在程序中开启事务1来执行sql,并获取A的余额同时锁住A这条数据。
# 事务1set autocommit=0;START TRANSACTION;# 获取A 的余额并存入A_balance变量:80SELECT user_id,@A_balance:=balance from account where user_id = "A" for UPDATE;
2、B用户给A用户转账30元,需在程序中开启事务2来执行sql,并获取B的余额同时锁住B这条数据。
# 事务2set autocommit=0;START TRANSACTION;# 获取A 的余额并存入A_balance变量:60SELECT user_id,@A_balance:=balance from account where user_id = "B" for UPDATE;
3、在事务1中执行剩下的sql
# 获取B 的余额并存入B_balance变量:60SELECT 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变量:60SELECT 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;
5、 为什么会出现这种情况呢?
3.5 死锁导致的问题
4、如何解决死锁问题?
4.1 打破请求和保持条件
/*** 事务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 打破相互获取锁条件(推荐)
/*** 事务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;"); }5、总结
因为mysql在互联网中的大量使用,所以死锁问题还是经常会被问到,希望兄弟们能掌握这方面的知识,提高自己的竞争力。
【相关推荐:mysql视频教程】
以上就是什么是死锁?聊聊对MySQL死锁的理解的详细内容,更多请关注php中文网其它相关文章!































































