标题
1.乐观锁:
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
乐观锁一般来说有以下2种方式:
- 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
- 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
Java JUC中的atomic包就是乐观锁的一种实现,AtomicInteger 通过CAS(Compare And Set)操作实现线程安全的自增。
2.悲观锁:
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。
3.举例:
乐观锁:
当对用户进行积分处理的时候就用到了乐观锁中的使用版本号控制
1.用户表的设计。
CREATE TABLE `yb_user` (
`user_id` varchar(64) NOT NULL,
`user_name` varchar(40) DEFAULT NULL,
`head_img_url` varchar(128) DEFAULT NULL COMMENT '头像',
`point` int(10) DEFAULT NULL COMMENT '总积分',
`version` bigint(20) DEFAULT '0' COMMENT '版本号',
`play_num` int(11) DEFAULT NULL COMMENT '每天可玩次数',
`created_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`user_id`),
UNIQUE KEY `id_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
2.对用户进行增加与减少积分的时候加上版本号
Mapper:
/**
* 新增积分
*
* @param userId 用户编号
* @param point 增加积分
* @param version 版本号
* @return
*/
int addPoint(@Param("userId") String userId,
@Param("point") Integer point,
@Param("version") int version);
/**
* 减少积分
*
* @param userId 用户编号
* @param point 增加积分
* @param version 版本号
* @return
*/
int lessPoint(@Param("userId") String userId,
@Param("point") Integer point,
@Param("version") int version);
xml:
<update id="addPoint">
update yb_user
set
point = point+#{point},
version = version+1
where user_id = #{userId} AND version=#{version}
</update>
<update id="lessPoint">
update yb_user
set
point = point-#{point},
version = version+1
where user_id = #{userId} AND version = #{version} AND point > #{point}
</update>
3.service层调用,我们可以看到当用户增加积分成功才进行接下来的操作,所以可以防止相同用户的并发操作
if (ybUserMapper.addPoint(userId, ybTask.getPoint(), user.getVersion()) > 0) {
pointDetail.setPoint(ybTask.getPoint());
if (taskType.equals("sing")) {
//答题记录的类型
pointDetail.setType(PointTypeEm.ANSWER.getCode());
} else {
//其他任务类型
pointDetail.setType(PointTypeEm.TASK_POINT.getCode());
}
ybPointDetailMapper.insertSelective(pointDetail);
}
悲观锁:
需要使用数据库的锁机制,比如SQL SERVER 的TABLOCKX(排它表锁) 此选项被选中时,SQL Server 将在整个表上置排它锁直至该命令或事务结束。这将防止其他进程读取或修改表中的数据。
SqlServer****中使用
Begin Tran
select top 1 @TrainNo=T_NO
from Train_ticket with (UPDLOCK) where S_Flag=0
update Train_ticket
set T_Name=user,
T_Time=getdate(),
S_Flag=1
where T_NO=@TrainNo
commit
我们在查询的时候使用了with (UPDLOCK)选项,在查询记录的时候我们就对记录加上了更新锁,表示我们即将对此记录进行更新. 注意更新锁和共享锁是不冲突的,也就是其他用户还可以查询此表的内容,但是和更新锁和排它锁是冲突的.所以其他的更新用户就会阻塞.
4.总结:
读的多,冲突几率小,乐观锁。
写的多,冲突几率大,悲观锁。
在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法.