一: 使用场景 |
(一) : 你是否在开发中会经常遇到下面的疑惑呢?
1、某段代码在并发的情况下可能会出现线程安全问题,该用什么方式来防止这种情况的出现呢?
2、可能你了解到可以使用Synchronized关键字来处理,但是Synchronized是通过悲观锁来实现的,你还会考虑到如果锁的范围太大,会很因影响到代码执行的效率,同时,Synchronized只适用于单机版,如果存在集群情况下(现在都流行分布式,这个情况也难免会遇到),它并不能保障不出现并发问题,那么,此时该使用什么方式来解决呢?
3、你可能又了解到可以使用Redis的分布式锁,或者使用Zookeeper来解决,但是这个又比较麻烦,是否还存在其他的方式呢?
4、面试时被问到: 在你开发过程中,保障线程安全除了使用Synchronized,你还使用过其他的方式?能够简单的说说使用的方式和原理?
(二) : 阅读完以上的问题,如果你在开发中有遇到过类似的,但没有找到比较好的解决方式的话,就继续往下阅读文章吧,或许你能够在其中找到一些些帮助。
二: 保证线程安全的新方法 |
(一)阅读完上面的问题后,你会发现使用Synchronized不能完全满足你的需求,使用Redis等分布式锁又比较麻烦,是不是还要更好的方法来处理这个问题呢? 是的,那就是我们本文的主角 For update,它可以帮你解决这个疑惑,从此走上发财致富的道理(笑…),下面我们就具体来了解下今天的主角吧。
(二)For Update是什么?
定义: for update是一种行级锁,又叫排它锁,一旦用户对表某个行记录施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行,如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。
看完定义后是不是感觉还是有点懵逼呀? 那么我们就用通俗的话来它是什么意思: 简单地讲for update就是将原来我们在代码里面加锁的情况转移到在数据库操作时进行加锁,通过它,我们不需要在代码逻辑中手动进行加锁,只需要在需要操作的记录时使用select 结合for update即可锁定数据,但是两个事务不能同时操作被锁定的数据,只有等一个事务提交完后另一个事务才只能操作,从而保证了线程安全问题。
通常情况下,select语句是不会对数据加锁,妨碍影响其他的DML和DDL操作。select … for update 语句是我们经常使用手工加锁语句。
(二)For Update有什么特点?
(1)查询不明确指定主键,如:(注意时测试时需要开启事务)
select * from user for update; // 锁住整个表
select * from user where id <> 3 for update; // 锁住整个表
......
(2)查询中没有主键的时候,如:
select * from user where name = 'abc' for update;// 锁住整个表
......
(1)明确指定主键,且数据存在时,如:(注意时测试时需要开启事务)
select * from user where id= 3 for update; // 锁住ID为3的行数据
(2)明确指定主键,但数据不存在时,如
select * from user where id= 30000 for update; // 没有锁
三: 实际使用场景 |
1、借助for update语句,我们可以在应用程序的层面手工实现数据加锁保护操作。就是那些需要业务层面数据独占时,可以考虑使用for update。
2、场景上,比如订票场景下,在应用显示有票,但是真正进行出票时,我们需要重新确定这个数据没有被其他客户端修改。所以,在这个确认过程中,可以使用for update。
四: 测试实例 |
(一)Mysql数据库测试结果(需要开启事务进行测试)
//mysql的innodb引擎(MyISAM引起无效)
// 注: Mysql测试时可以开启两个控制台进行,Postgresql可以借助Navicat软件
-- 一: 如果不开启事务的话,使用for update无法锁住表,增删查改都可以
-- 二: 只有一个窗口开启事务,并且锁定某个记录,另一个不开启事务时对数据库的操作,出现以下的情况
1、查询可以使用
2、更新不可以使用
3、删除记录不可以使用
4、插入记录也可以
-- 三: 只有一个开启事务,并且锁定整个表,另一个不开启事务时对数据库的操作,出现以下的情况
1、查询可以使用
2、插入可以使用
3、删除不可以使用
4、更新不可以使用
-- 四: 两边都开启事务时,出现以下的情况
1、查询可以使用
2、插入可以使用
3、删除不可使用
4、更新不可以使用
(二)PostgreSQL数据库测试结果(需要开启事务进行测试)
--- 没开启事务,使用for update时,两个窗口增删查改操作都可以
--- 一边开启事务,使用for update锁定某条记录,一边不开启事务,看是否能够插入,删除,更新,查询:
1、查询可以使用
2、更新不可以使用
3、删除记录不可以使用
4、插入记录也可以
insert into t_user(name) values('abc22'); -- 可以
update t_user set name ='测试' where id =4; -- 除了被锁定的记录外,可以修改其他的记录
delete from t_user where id =2; --除了被锁定的记录外,可以删除其他的记录
select * from t_user where id =6; -- 所以的记录都可以查询
--- 一边开启事务,使用for update锁定整个表,一边不开启事务,看是否能够插入,删除,更新,查询
1、查询可以使用
2、插入可以使用
3、删除不可以使用
4、更新不可以使用
insert into t_user(name) values('abc22'); -- 可以
update t_user set name ='测试' where id =9; -- 所有不可以被修改
delete from t_user where id =9; --所有的不可以被删除
select * from t_user where id =6; -- 所有的记录都可以查询
--- 两边同时开始事务,看是否能够插入,删除,更新,查询(锁定id为9的记录)
1、查询可以使用
2、插入可以使用
3、删除不可使用
4、更新不可以使用
begin;
insert into t_user(name) values('abc22'); -- 可以
update t_user set name ='测试' where id =9; -- 不可以被修改,除了记录9外都可以删除
delete from t_user where id =9; --不可以被删除,除了记录9外都可以删除
select * from t_user where id =6; -- 所有的记录都可以查询
commit;
五: 总结 |
通过上面的案例分析发现,For update可以很好的解决Synchronized集群情况下的线程安全问题以及替换使用分布式锁比较麻烦的问题,但是需要注意必须是在事务开启的前提下,同时,使用For update时需要尽量避免锁表的情况出现,有主键的时候尽量指定主键。
最后,非常感谢你的阅读,如果有什么疑问或者建议,欢迎在文章下方留言或者私信我,你的意见对我非常重要,你的肯定对我非常重要,如果你感觉本文对你有一点帮助,麻烦给我点一下赞和关注,后面还会书写更多的文章跟大家分享其他的知识。