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

2023-09-30 17:20:56 浏览数 (2)

一、前言

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

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

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

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

代码语言:javascript复制
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 人点赞