并发实战理解MySQL的锁(悲观锁+乐观锁)

首先简单介绍一下悲观锁和乐观锁:
悲观锁:
        比较悲观,一旦加锁,自身增删查改,其他线程无法任何操作,不能与其他锁并存。加锁方式 
for update
乐观锁:
        比较乐观,认为其他线程不会修改数据,一旦加锁自身可以增删查改,其他线程只能读。加锁方式 
lock in share mode
两种锁的的释放都在 commit或者rollback 之后,否则就会一直持有。

场景:并发查询签到时,导致一个用户可以签到多次

解决办法:for update 来解决并发重复查询,保证每次只有只能一个线程执行查询

一、mysql 测试
开启两个查询窗口,开启手动提交事务,先后执行一下加锁查询语句

set autocommit=0;
BEGIN;

SELECT
	id,
	uid,
	diamond
FROM
	reward 
WHERE
	uid = '2300220816449831' 
	AND type IN ('SIGNIN') 
	AND ctime >= '2022-10-17'
	AND ctime < '2022-10-19')
for update;
	
COMMIT;

其中一个窗口执行完成之后,只要不执行 commit 操作,另一个窗口就会一直阻塞着。这样就可以说明 for update 避免并发重复查询,每一次只允许一个线程查询。
这时候其实可以去修改或者查询跟查询条件无关的数据,发现是可以修改成功的,但是如果是同种类型的数据,就会被阻塞,说明for update 加的是行锁。

二、java代码测试
根据上面签到重复问题,可以在查询的时候,增加 for update,其实也就是步骤一中的sql语句,不过注意需要在方法上加事务注解 @Transactional(rollbackFor = Exception.class)

Jmeter 测试配置:100个线程同时访问

1.Jmeter 测试——查询无加for update

 发现同个用户id,同个时间点会有多条数据

2.Jmeter 测试——查询加for update,无@Transactional

结果发现也会重复插入数据

3.Jmeter 测试——查询加for update,加@Transactional

 同个用户id,同个时间点只有一条数据

三、加的什么锁

如果查询条件用了索引/主键,那么select ..... for update就会进行行锁。

如果是普通字段(没有索引/主键),那么select ..... for update就会进行锁表。

但是如果select没有数据为空情况,会怎样呢?

其实也会,变成锁表,这时候,如果有insert或者Update操作,就会出现死锁情况。所以for Update的查询结果,应该作为判空处理,而不是判断非空。

这种情况其实很好验证,只要包含where条件的查询数据清空了,然后用jmeter并发请求,就可以重现:Deadlock found when trying to get lock; try restarting transaction

总结:
       
1. for update可以加锁解决并发问题,并且还能作为分布式锁的一种实现方式,但是如果没有在事务内释放掉锁,就会导致死锁。
        2. for update使用必须在事务内,也就是必须加注解
@Transactional

        

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小明爱吃火锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值