业务场景:
通过数据库表来生成全局唯一的订单号。
实现细节:
该表的列
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 |









网友评论