实践悲观锁。
业务模型是User访问target,target的点击数量+1
一般流程是 读——count+1——写
如果在并发下,存在count计数失误的情况,可以以如下方法验证:
为了模拟放大并发的现象,在读与写之间➕ sleep
读-sleep(6000) —— count++ ——写
public PageResult detail(Integer userId, Integer id) { EquityMoney equityMoney = equityMoneyDao.findById(id); try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } equityMoney.setReading_quantity(equityMoney.getReading_quantity()+1); equityMoneyDao.save(equityMoney); return PageResult.genSuccess(equityMoney); }
@Query("SELECT cd from EquityMoney cd where cd.id=:id ") EquityMoney findById(@Param("id")Integer id);
测试配置1:
准备一台服务器A
准备浏览器两个标签,同时访问A
访问时两边同时sleep
结果两边结果都显示访问量(reading_quantity)为相同值
现在我们修改一下,为findById加上悲观锁
@Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT cd from EquityMoney cd where cd.id=:id ") EquityMoney findById(@Param("id")Integer id);
再行相同测试,此时看到访问量两个标签为递增状态了,说明悲观锁起作用了,线程1读取时加锁,导致线程2无法读取,直到线程1的reading_quantity+1并写入操作,事务完成,线程2才读取,此时再+1,所以两边访问量差1
注:
1 若换为PESSIMISTIC_READ,第一个请求成功,第二个请求报了
Deadlock found when trying to get lock; try restarting transaction
为什么?
查找了下资料:
(1)http://suene.iteye.com/blog/1756295
*
- @Lock(LockModeType.PESSIMISTIC_READ)
- public List<User> findByUsername(String username);
对应的 sql 就是:
- select * from t_user where username=? lock in share mode
*
- @Lock(LockModeType.PESSIMISTIC_WRITE)
- public List<User> findByUsername(String username);
对应的 sql 就是:
- select * from t_user where username=? for update
所以此二者的区别是 共享锁和排他锁
(2)http://blog.csdn.net/cug_jiang126com/article/details/50544728
SELECT ... LOCK IN SHARE MODE走的是IS锁(意向共享锁),即在符合条件的rows上都加了共享锁,这样的话,其他session可以读取这些记录,也可以继续添加IS锁,但是无法修改这些记录直到你这个加锁的session执行完成(否则直接锁等待超时)。
SELECT ... FOR UPDATE 走的是IX锁(意向排它锁),即在符合条件的rows上都加了排它锁,其他session也就无法在这些记录上添加任何的S锁或X锁。如果不存在一致性非锁定读的话,那么其他session是无法读取和修改这些记录的,但是innodb有非锁定读(快照读并不需要加锁),for update之后并不会阻塞其他session的快照读取操作,除了select ...lock in share mode和select ... for update这种显示加锁的查询操作。
(*快照读&当前读的概念:innodb当前读&快照读)
通过对比,发现for update的加锁方式无非是比lock in share mode的方式多阻塞了select...lock in share mode的查询方式,并不会阻塞快照读。
此外,分析一下这个为啥会死锁
在 PESSIMISTIC_READ & PESSIMISTIC_WRITE 与 共享锁 & 排它锁 中有详细解答
2 此悲观锁基于事务(因为mysql的锁基于单次会话,会话结束后就会解锁,试想一下,select for update 不加事务,等于执行完这句话就解锁了,等于没加),且一般在service层上加
@Transactional
测试结果
有事务注解 | 无事务注解 | |
innodb | ok | no |
myisam | no | / |
验证了实现行级悲观锁必须满足以下两个条件:
(1)innordb
(2)事务(确保同一mysql会话)
此外,Myisam引擎也可实现,但是需要显式调用table lock
数据库锁比起代码锁的好处:
(1)数据库锁适用于集群
(2)数据库锁可以精确到行,锁代码会大面积误伤不需要锁的其它操作
测试配置2:
准备两台服务器
A sleep
B none sleep
前提:
@Transcation+innodb
结果:
先访问A,再访问B
相同的id
发现B随着A的sleep一起阻塞了,证明id这条数据被锁导致B服务器线程阻塞
A id1,B id2
发现并未发生阻塞的情况,证明inndb mysql是行级锁,id1与id2并不互相影响
rr下,唯一索引当前读上reord lock,非唯一上 next-key lock
具体看:
相同id | 不同id | |
innodb | B阻塞 | B不阻塞 |
myisam | B不阻塞 | / |
注:全过程未涉及到某些博客提的 mysql autocommit
应该是由spring 的@Transaction注解 和 hibernate代劳了
参考:
1.以一个经典财务案例并配合图讲述了为何要用锁,用代码锁?还是数据库锁?
2.使用乐观锁的代码
3.使用悲观锁的代码
1.高度概述了spring data jpa 的使用
2.点出了悲观锁中 PESSIMISTIC_READ 与 PESSIMISTIC_WRITE在编译后的sql区别,在于 lock in share mode 和 for update
深入理解SELECT ... LOCK IN SHARE MODE和SELECT ... FOR UPDATE
1.讲述了 共享锁和排它锁的区别,最主要在于共享锁阻塞写,排它锁阻塞读写
2.以一个经典订单案例讲述了为何要用锁,描述了一个经典死锁案例
3.总结了一般应用场景-共享:跨表 排它:同表一致性要求
1.详细描述了oracle和mysql的锁
2.以经典订单案例,描述了加锁前与加悲观锁后的效果
3.提到了autocommit(mysql),虽然我没用到
4.以大量实例证明 mysql 行级锁、表级锁随着明确指定主键或索引的关系
MySQL数据库锁机制之MyISAM引擎表锁和InnoDB行锁详解
主要室myisam和innodb区别,没怎么看
1.简单清晰的一个spring data jpa 悲观行级锁(排它)的代码实例
2.强调了service层开启事务
3.强调了必须明确主键(我认为或添置索引),才会启用行级锁,否则为表级锁
1.自上而下简述了spring data jpa 锁种类
2.简单清晰的一个spring data jpa 悲观行级锁(共享,我个人认为这里应该用排它,共享锁阻塞save操作,这个例子感觉室死锁)的代码实例
3.强调了service层开启事务
8.16日语:
能否
update xxx cd set cd.reading_qualtity = cd.reading_qualtity+1 where cd.id=:id