业务场景:
通过数据库表来生成全局唯一的订单号。
实现细节:
该表的列
current_value是全局唯一的订单号,select出来即可作为订单号使用。
step是订单号增长步伐,当此次操作使用了current_value的值作为订单号之后,需要current_value的值以便下个订单使用。更新的方式就是为current_value的值加上step的值。
会出现的问题:
假设有两个事务,这个两个事务的操作都是先获取最新的全局唯一订单号,再更新表。
发生时间编号 | 事务a | 事务b |
---|---|---|
1 | begin | |
2 | begin | |
3 | select * from sequence_info where name = 'order_info' | |
4 | select * from sequence_info where name = 'order_info' |
这个时候事务a和事务b获取到current_value值是一样的,我们假设是100。
发生时间编号 | 事务a | 事务b |
---|---|---|
5 | update sequence_info set current_value = #{currentValue}+#{step} where name = 'order_info' | |
6 | commit | |
7 | update sequence_info set current_value = #{currentValue}+#{step} where name = 'order_info' | |
8 | commit |
到这里,不仅仅有两个订单的订单号是一样的(100,即两个事务取到的current_value的值),而且两次更新按道理说current_value的值应该是增长2个step,但目前只增长了1个step。
解决办法:
在select的时候为记录加上X型正经记录锁(X型正经记录锁这个叫法只是我的个人叫法,读者可以理解为排他锁即可)
发生时间编号 | 事务a | 事务b |
---|---|---|
1 | begin | |
2 | begin | |
3 | select * from sequence_info where name = 'order_info' for update |
这个时候事务b再想select就需要阻塞等待事务a提交了,因为排他锁与排他锁之间不兼容。
发生时间编号 | 事务a | 事务b |
---|---|---|
4 | update sequence_info set current_value = #{currentValue}+#{step} where name = 'order_info' | |
5 | commit | |
6 | select * from sequence_info where name = 'order_info' for update | |
7 | update sequence_info set current_value = #{currentValue}+#{step} where name = 'order_info' | |
8 | commit |
其实还有另外一种类似的解决方案:
通过update也是可以给记录加上X型正经记录锁的。
发生时间编号 | 事务a | 事务b |
---|---|---|
1 | begin | |
2 | update sequence_info SET current_value = current_value+step WHERE NAME = 'order_info' | |
3 | select * from sequence_info where name = 'order_info' | |
4 | commit |
由于update操作为记录添加的是X型正经记录锁,所以只要事务a未提交,别的事务也不能修改记录,甚至是都不能读取。
发生时间编号 | 事务a | 事务b |
---|---|---|
5 | begin | |
6 | update sequence_info SET current_value = current_value+step WHERE NAME = 'order_info' | |
7 | select * from sequence_info where name = 'order_info' | |
8 | commit |
网友评论