一个关于数据库级别的并发问题

诺浅
Java领域优质创作者
2017-01-20 06:36:39
需求:流水表的订单号对于交易A是允许重复的,但是对于交易B是不允许重复的。程序需要部署负载均衡。
设计:
交易A可以直接插入数据库
交易B需要先根据订单号去数据库查询下有没有这条数据,没有才执行插入
问题:
如果B交易有并发请求的时候(订单号相同)查的时候是没有数据但是查询完之后另一个并发请求插入了数据,然后这个查询的也插入了数据,那就会造成B交易在数据库有重复订单号的情况。
图示:

解决方案:请问怎么解决这个问题。
...全文
928 27 打赏 收藏 转发到动态 举报
写回复
用AI写文章
27 条回复
切换为时间正序
请发表友善的回复…
发表回复
诺浅 2017-04-10
  • 打赏
  • 举报
回复
引用 25 楼 u011619071 的回复:
[quote=引用 24 楼 qq32933432 的回复:] 经过测试发现还有一种方案可以,就是使用悲观锁,我用的是JPA,
@Lock(value = LockModeType.PESSIMISTIC_READ)
    @Query(value="select t from TBlvcAccessIpConfig t")
	public List test1();
通过添加Lock注解即可,发现发送的查询语句后面会带上for update,即使用了数据库本身的锁机制。如果对JPA不熟采用直接写SQL的形式也是可以的
@Transactional
	@Query(value="select * from t_blvc_access_ip_config for update",nativeQuery=true)
	public List test1();
悲观锁希望楼主慎重一些,for update是会锁定对应结果集的,如果此时结果集中的数据发生改变,线上数据就会引起死锁。会有额外的运维成本。[/quote]嗯,明白,但是这个问题你有什么好的解决方案分享一下吗?似乎只能让前端控制订单号不重复。
X元素 2017-04-10
  • 打赏
  • 举报
回复
引用 26 楼 qq32933432 的回复:
[quote=引用 25 楼 u011619071 的回复:] [quote=引用 24 楼 qq32933432 的回复:] 经过测试发现还有一种方案可以,就是使用悲观锁,我用的是JPA,
@Lock(value = LockModeType.PESSIMISTIC_READ)
    @Query(value="select t from TBlvcAccessIpConfig t")
	public List test1();
通过添加Lock注解即可,发现发送的查询语句后面会带上for update,即使用了数据库本身的锁机制。如果对JPA不熟采用直接写SQL的形式也是可以的
@Transactional
	@Query(value="select * from t_blvc_access_ip_config for update",nativeQuery=true)
	public List test1();
悲观锁希望楼主慎重一些,for update是会锁定对应结果集的,如果此时结果集中的数据发生改变,线上数据就会引起死锁。会有额外的运维成本。[/quote]嗯,明白,但是这个问题你有什么好的解决方案分享一下吗?似乎只能让前端控制订单号不重复。[/quote] 不能让前端来控制数据,还是要根据你的业务来做。楼主仔细分析一下,考虑分布式服务跟项目的扩展性即可。
X元素 2017-02-08
  • 打赏
  • 举报
回复
引用 24 楼 qq32933432 的回复:
经过测试发现还有一种方案可以,就是使用悲观锁,我用的是JPA,
@Lock(value = LockModeType.PESSIMISTIC_READ)
    @Query(value="select t from TBlvcAccessIpConfig t")
	public List test1();
通过添加Lock注解即可,发现发送的查询语句后面会带上for update,即使用了数据库本身的锁机制。如果对JPA不熟采用直接写SQL的形式也是可以的
@Transactional
	@Query(value="select * from t_blvc_access_ip_config for update",nativeQuery=true)
	public List test1();
悲观锁希望楼主慎重一些,for update是会锁定对应结果集的,如果此时结果集中的数据发生改变,线上数据就会引起死锁。会有额外的运维成本。
诺浅 2017-02-04
  • 打赏
  • 举报
回复
经过测试发现还有一种方案可以,就是使用悲观锁,我用的是JPA,
@Lock(value = LockModeType.PESSIMISTIC_READ)
    @Query(value="select t from TBlvcAccessIpConfig t")
	public List test1();
通过添加Lock注解即可,发现发送的查询语句后面会带上for update,即使用了数据库本身的锁机制。如果对JPA不熟采用直接写SQL的形式也是可以的
@Transactional
	@Query(value="select * from t_blvc_access_ip_config for update",nativeQuery=true)
	public List test1();
诺浅 2017-01-23
  • 打赏
  • 举报
回复
引用 14 楼 bbqqqbq 的回复:
你自己的方案可行: 没有,如果用C表的话我是把C表有个订单号那一列设置唯一索引的,这样B交易插入这张表的时候是会报错的,而对于A交易是不去操作这张表的。 将插入C表的操作和插入C成功后的操作放在一个事务里就行了。
可行理论应该是可行的,就是不知道在真实的交易系统中是否是这样做的,想知道是否有更好的解决方案,这个为了一个交易而多加一张表其实感觉不太合理。
bbqqqbq 2017-01-23
  • 打赏
  • 举报
回复
你自己的方案可行: 没有,如果用C表的话我是把C表有个订单号那一列设置唯一索引的,这样B交易插入这张表的时候是会报错的,而对于A交易是不去操作这张表的。 将插入C表的操作和插入C成功后的操作放在一个事务里就行了。
X元素 2017-01-23
  • 打赏
  • 举报
回复
引用 11 楼 qq32933432 的回复:
[quote=引用 9 楼 u011619071 的回复:] [quote=引用 4 楼 qq32933432 的回复:] [quote=引用 3 楼 u011619071 的回复:] [quote=引用 2 楼 qq32933432 的回复:] 有人吗?求解答呀
楼主我有个小小的建议,为什么不考虑把订单号设计成 兼容负载的呢? 你这一步如果是单节点没问题, 如果B交易有并发请求的时候(订单号相同)查的时候是没有数据但是查询完之后另一个并发请求插入了数据 可以考虑 在查的时候 加一把公共资源锁 ,为了保证 所有负载的服务可以及时获取到相关锁信息。[/quote] 你说的加公共资源锁的具体实现是什么?能麻烦说下吗?之前有想过,在插入订单表之前先插入一张C表,然后C表设置一个唯一索引,只有C表插入成功的才接着插入订单表。这样似乎可以解决这个问题。然后想知道有没有更好的实现方案。[/quote] 楼主都已经知道是并发引起的数据重复,那你设计的C表也是会出现这个问题的。因为你的操作都是SELECT --> INSERT, 只要并发操作存在,这个动作就会引起数据重复 楼主可以参考 redis 分布式锁,想简单的话可以利用自旋锁的方式,实现自己业务逻辑,也可以根据自己的业务搭建锁服务, [/quote] 没有,如果用C表的话我是把C表有个订单号那一列设置唯一索引的,这样B交易插入这张表的时候是会报错的,而对于A交易是不去操作这张表的。[/quote] 这张C表要如何设计 唯一索引 主键自增? 还是类UUID方式,如果从服务的扩展角度来考虑,后期要分表的话,你的自增怎么办,个人愚见,如果你只是要实现等幂操作不会引起你的订单重复的话,考虑一下实现有意义的订单号,并发操作下仍不会重复的订单号即可。
stpilot 2017-01-23
  • 打赏
  • 举报
回复
来个代码看看,学习学习,蟹蟹
诺浅 2017-01-23
  • 打赏
  • 举报
回复
引用 20 楼 bbqqqbq 的回复:
[quote=引用 15 楼 qq32933432 的回复:] [quote=引用 14 楼 bbqqqbq 的回复:] 你自己的方案可行: 没有,如果用C表的话我是把C表有个订单号那一列设置唯一索引的,这样B交易插入这张表的时候是会报错的,而对于A交易是不去操作这张表的。 将插入C表的操作和插入C成功后的操作放在一个事务里就行了。
可行理论应该是可行的,就是不知道在真实的交易系统中是否是这样做的,想知道是否有更好的解决方案,这个为了一个交易而多加一张表其实感觉不太合理。[/quote] 看了你上面的需求,你其实要解决的问题应该是避免请求的重复提交吧?这个重复请求的处理看你在什么地方了。 解决方案1:发送请求时控制,即请求1和请求2是同一订单号A,假设请求的原始数据为订单A,订单A发了两个请求,即请求1和请求2,需要控制住如果订单A发了一次请求后,不允许再发请求2. 在订单A发请求时,加一个锁定的状态,只允许发一次请求。 解决方案2:如果请求不受你的控制或者说这个请求是其他系统的事情,他们只负责发请求,你这边要控制的话,按照你的想法,加一张表来记录这个订单号,唯一性控制。这种方法我个人觉得挺好的。如果你要通过写代码,锁的形式就变得复杂了。 [/quote] 好吧,通过大家的讨论似乎确实只有这两种方案,不知道前面他们说的存储过程能否实现,改天试试。
qq_34712076 2017-01-23
  • 打赏
  • 举报
回复
uuid不是永远不会重复吗
bbqqqbq 2017-01-23
  • 打赏
  • 举报
回复
引用 15 楼 qq32933432 的回复:
[quote=引用 14 楼 bbqqqbq 的回复:] 你自己的方案可行: 没有,如果用C表的话我是把C表有个订单号那一列设置唯一索引的,这样B交易插入这张表的时候是会报错的,而对于A交易是不去操作这张表的。 将插入C表的操作和插入C成功后的操作放在一个事务里就行了。
可行理论应该是可行的,就是不知道在真实的交易系统中是否是这样做的,想知道是否有更好的解决方案,这个为了一个交易而多加一张表其实感觉不太合理。[/quote] 看了你上面的需求,你其实要解决的问题应该是避免请求的重复提交吧?这个重复请求的处理看你在什么地方了。 解决方案1:发送请求时控制,即请求1和请求2是同一订单号A,假设请求的原始数据为订单A,订单A发了两个请求,即请求1和请求2,需要控制住如果订单A发了一次请求后,不允许再发请求2. 在订单A发请求时,加一个锁定的状态,只允许发一次请求。 解决方案2:如果请求不受你的控制或者说这个请求是其他系统的事情,他们只负责发请求,你这边要控制的话,按照你的想法,加一张表来记录这个订单号,唯一性控制。这种方法我个人觉得挺好的。如果你要通过写代码,锁的形式就变得复杂了。
诺浅 2017-01-23
  • 打赏
  • 举报
回复
引用 18 楼 qq_26893375 的回复:
你这个订单号是怎么生成的
订单号前端传的,不同的team,不好沟通,你懂的。
qq_26893375 2017-01-23
  • 打赏
  • 举报
回复
你这个订单号是怎么生成的
qq_26893375 2017-01-23
  • 打赏
  • 举报
回复
我擦 看错了
qq_26893375 2017-01-23
  • 打赏
  • 举报
回复
你B表的订单号做唯一索引不行吗
_jant 2017-01-22
  • 打赏
  • 举报
回复
Mark、Mark、Mark
ylovep 2017-01-22
  • 打赏
  • 举报
回复
建议把订单号生产写成一个服务或者单例 写生成单据号的方法为同步即加锁 这样保证获取的单据号是不重复的
诺浅 2017-01-22
  • 打赏
  • 举报
回复
引用 3 楼 u011619071 的回复:
[quote=引用 2 楼 qq32933432 的回复:] 有人吗?求解答呀
楼主我有个小小的建议,为什么不考虑把订单号设计成 兼容负载的呢? 你这一步如果是单节点没问题, 如果B交易有并发请求的时候(订单号相同)查的时候是没有数据但是查询完之后另一个并发请求插入了数据 可以考虑 在查的时候 加一把公共资源锁 ,为了保证 所有负载的服务可以及时获取到相关锁信息。[/quote] 你说的加公共资源锁的具体实现是什么?能麻烦说下吗?之前有想过,在插入订单表之前先插入一张C表,然后C表设置一个唯一索引,只有C表插入成功的才接着插入订单表。这样似乎可以解决这个问题。然后想知道有没有更好的实现方案。
诺浅 2017-01-22
  • 打赏
  • 举报
回复
引用 8 楼 trocp 的回复:
[quote=引用 7 楼 trocp 的回复:] 用存储过程。 存储过程可以保证一次操作在一个事务中,并能保证操作的原子性。
把查询订单号和插入订单号这两个操作放在一个存储过程中,程序只需要调用这个存储过程即可。 各大关系性数据库均支持存储过程。[/quote] 这个似乎是一个好的办法。。。想问下关系型数据库对一个存储过程的操作是否是单线程的?不会存在多个线程同时调用同一个存储过程的情况吧?
诺浅 2017-01-22
  • 打赏
  • 举报
回复
引用 9 楼 u011619071 的回复:
[quote=引用 4 楼 qq32933432 的回复:] [quote=引用 3 楼 u011619071 的回复:] [quote=引用 2 楼 qq32933432 的回复:] 有人吗?求解答呀
楼主我有个小小的建议,为什么不考虑把订单号设计成 兼容负载的呢? 你这一步如果是单节点没问题, 如果B交易有并发请求的时候(订单号相同)查的时候是没有数据但是查询完之后另一个并发请求插入了数据 可以考虑 在查的时候 加一把公共资源锁 ,为了保证 所有负载的服务可以及时获取到相关锁信息。[/quote] 你说的加公共资源锁的具体实现是什么?能麻烦说下吗?之前有想过,在插入订单表之前先插入一张C表,然后C表设置一个唯一索引,只有C表插入成功的才接着插入订单表。这样似乎可以解决这个问题。然后想知道有没有更好的实现方案。[/quote] 楼主都已经知道是并发引起的数据重复,那你设计的C表也是会出现这个问题的。因为你的操作都是SELECT --> INSERT, 只要并发操作存在,这个动作就会引起数据重复 楼主可以参考 redis 分布式锁,想简单的话可以利用自旋锁的方式,实现自己业务逻辑,也可以根据自己的业务搭建锁服务, [/quote] 没有,如果用C表的话我是把C表有个订单号那一列设置唯一索引的,这样B交易插入这张表的时候是会报错的,而对于A交易是不去操作这张表的。
加载更多回复(7)

67,518

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧