for update 死锁场景

for update 死锁场景

解决幻读的问题

在业务中有这么一个场景,查询id 为 30 的记录,如果存在更新,不存在就插入

正常逻辑代码

事物F1


result =  select * from rt_shop where id = 30;
if(null == result){
    inset into rt_shop set column_name1 = value1, column_name2 = value2,…;
 }else{
    update rt_shop set column_name1 = value1, column_name2 = value2,…;
 }

事物F2

delete from rt_shop where id  = 30;

存在的问题

F1 中 因为 查询(DQL) 和 操作(DML) 是两个分开的操作,因为DQL 默认是不加任何锁的, DQL 成功后 ,其他事物F2 依然可以对 这个记录进行 DML, 所以当F2 删除了这个 记录,那么F1 进行更新的时候就会报错!

这个是时候就产生了幻读,之前明明是有的,那么现在更新的时候就没了,所以就操作失误了;那么如何解决这个幻读问题呢?一般来讲解决幻读都是采用 next-key(范围锁+行锁)

使用next-key解决幻读的问题

本次需要处理的场景是 查询id 为 30 的记录,如果有的话就更新,没有的话就新增

没有id 为 30 的场景

代码案例 ,这是一个表

CREATE TABLE `rt_shop` (
 `id` int(11) NOT NULL AUTO_INCREMENT, 
 `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '店铺名称', 
 `recruiter_id` int(11) NOT NULL COMMENT '顾问ID',
 `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '店铺照片', `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 0 否 1是', 
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), 
 KEY `idx.recruiter_id` (`recruiter_id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8 COMMENT='店铺表';

数据

id name   recruiter_id   image  is_deleted create_time    update_time
29 宫本无错   1  xx 0  2021-05-31 17:50:29    2021-06-03 15:54:58
31 李的店铺   4  https://st1-d172YjKq1eTNWhLj.png   0  2021-05-31 17:50:29    2021-06-16 17:58:06

业务代码代码解释

事务T1


start transaction;

select * from rt_shop where id  = 30 for update;

INSERT INTO `rt_shop` (`id`, `name`, `recruiter_id`, `image`, `is_deleted`, `create_time`, `update_time`)
VALUES
 (30, '慢跑的店铺', 3, 'https://thTLZ11nhOvy52MMy6uJOia45oIdt6qbriafmI10wLElNbg5DGVXTuCNQzezuaQvjMYVib74TSLtu0oC2Q/132', 0, '2021-05-31 17:50:29', '2021-05-31 17:50:29');
commit

事务T2

start transaction;
select * from rt_shop where id  = 30 for update;

INSERT INTO `rt_shop` (`id`, `name`, `recruiter_id`, `image`, `is_deleted`, `create_time`, `update_time`)
VALUES
 (30, '慢跑的店铺', 3, 'https://5oIdt6qbriafmI10wLElNbg5DGVXTuCNQzezuaQvjMYVib74TSLtu0oC2Q/132', 0, '2021-05-31 17:50:29', '2021-05-31 17:50:29');
commit

如果并发场景下,先执行了事物A,如果有id是30 的数据,那么就会加行锁,属于独占锁,T2的for update 就无法加锁,会阻塞串行,不会发生死锁

如果没有 id为 30的 数据,那么就会 加一个范围锁 ,范围锁的范围就是 {29,31},因为范围锁不是独占锁,所以T2也可以进行加锁;

这个时候当事物 T1 想添加一个id 为 30的数据,因为 T2 已经加了范围锁,所以插入的时候检测到有一个T2的锁,所以就等待T2释放锁,所以会一直阻塞

T2 往下执行的时候也有这个问题,所以都在等待对方,就发生了死锁的问题!,会返回如下代码

Deadlock found when trying to get lock; try restarting transaction

所以范围锁也不能完全解决问题,需要有一个新的思路

解决方案

解决命令

insert into test (a,b) values (1,2) on duplicate key update b = b + 1;

mysql “ON DUPLICATE KEY UPDATE” 语法

如果在INSERT语句末尾指定了ON DUPLICATE KEY UPDATE,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则在出现重复值的行执行UPDATE;如果不会导致唯一值列重复的问题,则插入新行。

INSERT INTO TABLE (a,c) VALUES (1,3) ON DUPLICATE KEY UPDATE c=c+1;
相当于下面
UPDATE TABLE SET c=c+1 WHERE a=1;

如果行作为新记录被插入,则受影响行的值显示1;如果原有的记录被更新,则受影响行的值显示2
本作品采用《CC 协议》,转载必须注明作者和本文链接
good good study day day up
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!