订单取消和支付成功并发问题

这是一个非常经典且重要的分布式系统问题。订单取消和支付成功同时发生,本质上是一个资源竞争问题,核心在于如何保证两个并发操作对订单状态的修改满足业务的最终一致性(即一个订单最终只能有一种确定的状态)。


核心业务原则与状态机

首先,必须明确一个坚不可摧的业务规则:一个订单一旦支付成功,就绝不能被取消。 反之,一个订单如果先被成功取消,那么后续的支付也应该失败。

这通常通过订单状态机来实现:

  • 初始状态:待支付
  • 可变为:已支付 (支付操作) 或 已取消 (取消操作)
  • 已支付已取消 是两个终点状态,不能再相互转换。

我们的所有技术方案都是为了保护这个状态机的正确流转。


方案一:数据库悲观锁 (Pessimistic Locking) - 简单粗暴

适用场景: 单体架构,数据库压力不大,并发冲突相对较少的场景。

实现原理: 在修改订单状态前,先通过 SELECT ... FOR UPDATE 锁定数据库中的这条订单记录。这样其他事务在尝试修改这条记录时会被阻塞,直到锁被释放,从而保证了操作的串行化。

实现步骤(以支付和取消同时发生为例):

  1. 无论是支付回调逻辑还是取消逻辑,在开始处理时,首先开启一个数据库事务。
  2. 在事务中,使用 SELECT * FROM orders WHERE order_id = ? FOR UPDATE 查询并锁定订单记录。
  3. 检查订单的当前状态:
    • 如果状态是 待支付,则继续执行支付或取消操作,更新状态。
    • 如果状态已经是 已支付,则取消操作应识别到此状态,直接返回“取消失败,订单已支付”并结束逻辑。
    • 如果状态已经是 已取消,则支付操作应识别到此状态,直接返回“支付失败,订单已关闭”并结束逻辑。
  4. 提交事务,释放锁。

优点:

  • 实现简单,直接利用数据库的能力。
  • 强一致性保证。

缺点:

  • 性能瓶颈:并发高时,大量请求会被阻塞,数据库连接池容易被耗尽的,响应时间变长。
  • 不适合分布式系统:如果应用是集群部署,多个节点间的数据库连接是共享的,这个方法仍然有效,但数据库本身会成为单点瓶颈。
  • 死锁风险:需要仔细控制业务的加锁顺序。

方案二:数据库乐观锁 (Optimistic Locking) - 推荐常用

适用场景: 绝大多数并发场景,特别是读多写少的情况。单体和服务化架构都适用。

实现原理: 不对数据加锁,而是通过一个版本号(version)字段或时间戳来实现。在更新时,检查版本号是否和最初读取时一致,如果一致则更新成功并版本号+1,否则更新失败,意味着数据已被其他操作修改过。

实现步骤:

  1. 订单表增加一个 version 字段(或使用 update_time 时间戳)。
  2. 支付或取消逻辑开始时,先查询出订单的当前状态和当前 version(例如 version=1)。
  3. 根据业务逻辑计算下一个状态。
  4. 执行更新操作:
    UPDATE orders 
    SET status = '已支付', version = version + 1 
    WHERE order_id = ? AND version = 1; -- 这里version是之前查出来的值
    
    UPDATE orders 
    SET status = '已取消', version = version + 1 
    WHERE order_id = ? AND version = 1;
    
  5. 检查更新语句的影响行数(affected rows):
    • 如果影响行数为 1,说明更新成功,抢到了资源。
    • 如果影响行数为 0,说明 WHERE 条件不成立(即 version 已经变了),更新失败。此时可以重新查询订单的最新状态,并告知用户“操作失败,请重试”或根据最新状态进行后续处理(例如支付时发现订单已取消,则进行退款)。

优点:

  • 性能比悲观锁好很多,避免了数据库锁的开销。
  • 适用于分布式环境。

缺点:

  • 需要处理更新失败的情况,业务逻辑稍复杂(通常需要重试或提示用户)。
  • 如果冲突频率非常高,频繁的重试反而会降低性能。

方案三:状态机 + 数据库唯一约束 - 优雅幂等

适用场景: 作为辅助手段,与乐观锁结合使用,提供更强的幂等性和一致性保证。

实现原理: 利用数据库的唯一键约束,来防止订单状态被重复更新。例如,可以创建一张“订单状态变更流水表”。

  1. 创建订单状态流水表 order_status_log:
    CREATE TABLE order_status_log (id BIGINT PRIMARY KEY AUTO_INCREMENT,order_id BIGINT NOT NULL,from_status VARCHAR(20),to_status VARCHAR(20) NOT NULL,create_time DATETIME,-- 唯一约束:一个订单的每次状态变更必须是唯一的UNIQUE KEY uk_order_id_status (order_id, from_status, to_status)
    );
    
  2. 在更新订单主表状态时,在同一数据库事务中,向流水表插入一条记录。
    • 例如,从 待支付 变更为 已支付,则插入 (order_id, ‘待支付’, ‘已支付’, now())
  3. 如果两个操作同时发生,比如支付和取消都通过了乐观锁的版本检查,尝试更新主表和插入流水表。由于流水表的 UNIQUE KEY uk_order_id_status (order_id, from_status, to_status) 约束,第二个插入操作必然会失败,从而导致整个事务回滚。
  4. 最终只有一个操作能成功。

优点:

  • 提供了极强的数据一致性保证,几乎不可能出现状态错乱。
  • 流水表本身也具有审计和追溯的价值。

缺点:

  • 业务逻辑更复杂,需要维护两张表。
  • 依然依赖数据库事务。

方案四:消息队列 + 最终一致性 - 分布式解耦

适用场景: 大型分布式系统,业务解耦要求高,吞吐量大的场景。

实现原理: 将同步的、可能冲突的操作,通过消息队列串行化处理。支付成功和取消请求都先发送到消息队列,由一个消费者按顺序逐个处理,从而从根本上避免并发冲突。

实现步骤:

  1. 消息生产:
    • 支付回调异步通知和用户取消请求,都不直接处理业务逻辑。
    • 它们只负责校验基础参数,然后向一个特定主题的消息队列(如 RocketMQ/Kafka)发送一条消息。消息体包含订单ID和操作类型(e.g., {"orderId": 123, "event": "PAYMENT_SUCCESS"})。
  2. 消息消费:
    • 创建一个顺序消息消费者,监听这个主题。确保同一个订单ID的消息被发送到同一个MessageQueue,并被同一个消费者顺序处理
    • 消费者接收到消息后:
      a. 开启事务。
      b. 查询订单(无需FOR UPDATE,因为消息是顺序的)。
      c. 检查订单状态机:
      - 若当前状态允许执行目标操作(如状态是待支付,目标是已支付),则更新状态。
      - 若不允许(如状态已是已取消,目标是已支付),则丢弃消息或记录日志(说明发生了冲突,但由消费者优雅处理了)。
      d. 提交事务。
      e. 消费成功,确认消息。

优点:

  • 高吞吐量,性能好。
  • 彻底解耦,支付和取消的发起方不需要等待业务处理完成。
  • 通过顺序消息天然解决了并发问题。

缺点:

  • 架构复杂,引入了消息队列组件。
  • 一致性是最终一致性,处理会有毫秒级或秒级的延迟。
  • 需要保证消息的可靠投递和不重复消费(幂等),通常消息队列本身能提供Exactly-OnceAt-Least-Once语义,消费端需要做幂等(方案二的乐观锁或方案三的状态流水正好可以用来做幂等)。

方案五:分布式锁 - 通用方案

适用场景: 分布式系统,需要对某个分布式资源进行互斥访问。

实现原理: 在执行业务逻辑前,先尝试获取一个基于订单ID的分布式锁(如 Redis 的 SET order_id:123 random_value NX EX 30)。只有拿到锁的操作才能继续执行查询和更新订单状态的逻辑。

实现步骤:

  1. 支付或取消逻辑开始时,先尝试获取指定 order_id 的分布式锁。
  2. 获取成功,则继续执行数据库查询和更新逻辑(此时可以用简单的先查后改,因为锁保证了互斥)。
  3. 获取失败,则重试或直接返回“系统繁忙,请稍后再试”。
  4. 业务逻辑处理完成后,释放分布式锁。

优点:

  • 通用性强,不依赖于数据库特性。
  • 适用于任何需要互斥访问的分布式场景。

缺点:

  • 引入新的组件(如Redis),增加了系统复杂性。
  • 如果锁失效时间设置不当,可能导致锁提前释放(业务没做完)或死锁(业务做完锁没释放)。
  • 性能开销比数据库乐观锁大。

总结与选型建议

方案复杂度一致性性能适用架构核心思想
悲观锁强一致性单体先加锁,再操作
乐观锁最终一致性单体/分布式无锁检测,失败重试
状态机+约束强一致性单体/分布式利用数据库约束防冲突
消息队列最终一致性极好分布式异步化与串行化
分布式锁最终一致性分布式外部组件实现互斥

给你的建议:

  1. 新手或初创项目:优先使用 方案二(乐观锁)。它在复杂性、性能和一致性上取得了很好的平衡,是处理这类问题最常用的手段。
  2. 中等规模项目:采用 方案二 + 方案三 组合。用乐观锁做更新,用状态流水表做幂等、审计和双重保险,非常稳健。
  3. 大型分布式系统:采用 方案四(消息队列)。将并发请求转为顺序处理,是解决高并发问题的终极方案之一,同时也能很好地解耦系统。
  4. 方案一(悲观锁) 尽量少用,除非你非常确定并发量不高。
  5. 方案五(分布式锁) 更适用于更广泛的分布式资源竞争场景,单纯为了订单状态这个问题,通常优先选择乐观锁或消息队列。

最终,选择哪种方案取决于你的业务规模、技术架构和团队对复杂性的承受能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/920544.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920544.shtml
英文地址,请注明出处:http://en.pswp.cn/news/920544.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

rabbitmq学习笔记 ----- 多级消息延迟始终为 20s 问题排查

问题现象 在实现多级延迟消息功能时&#xff0c;发现每次消息延迟间隔始终为20s&#xff0c;无法按照预期依次使用20s→10s→5s的延迟时间。日志显示每次处理时移除的延迟时间都是20000L。 问题代码片段 1.生产者 Testvoid sendDelayMessage2() {List<Long> expireTimeLi…

软件测试(三):测试流程及测试用例

1.测试流程1.需求分析进行测试之前先阅读需求文档&#xff0c;分析指出不合理或不明确的地方2.计划编写与测试用例测试用例用例即&#xff1a;用户使用的案例测试用例&#xff1a;执行测试的文档作用&#xff1a;用例格式&#xff1a;----------------------------------------…

Python:列表的进阶技巧

列表&#xff08;list&#xff09;作为 Python 最常用的数据结构之一&#xff0c;不仅能存储有序数据&#xff0c;还能在推导式、函数参数传递、数据处理等场景中发挥强大作用。下面介绍一些进阶技巧与常见应用。一、去重与排序1、快速去重&#xff08;不保序&#xff09;nums …

【完整源码+数据集+部署教程】硬币分类与识别系统源码和数据集:改进yolo11-SWC

背景意义 随着经济的发展和数字支付的普及&#xff0c;传统硬币的使用逐渐减少&#xff0c;但在某些地区和特定场合&#xff0c;硬币仍然是重要的支付手段。因此&#xff0c;硬币的分类与识别在自动化支付、智能零售和物联网等领域具有重要的应用价值。尤其是在银行、商超和自助…

莱特莱德:以“第四代极限分离技术”,赋能生物发酵产业升级

莱特莱德&#xff1a;以“第四代极限分离技术”&#xff0c;赋能生物发酵产业升级Empowering Upgrades in the Bio-Fermentation Industry with "Fourth-Generation Extreme Separation Technology生物发酵行业正经历从 “规模扩张” 向 “质效提升” 的关键转型&#xff…

外卖大战之后,再看美团的护城河

美团&#xff08;03690.HK&#xff09;于近日发布了2025年Q2财报&#xff0c;市场无疑将更多目光投向了其备受关注的外卖业务上。毫无悬念&#xff0c;受外卖竞争和加大投入的成本影响&#xff0c;美团在外卖业务上的财务数据受到明显压力&#xff0c;利润大幅下跌&#xff0c;…

R包fastWGCNA - 快速执行WGCNA分析和下游分析可视化

最新版本: 1.0.0可以对着视频教程学习和使用&#xff1a;然而还没录呢, 关注B站等我更新R包介绍 开发背景 WGCNA是转录组或芯片表达谱数据常用得分析, 可用来鉴定跟分组或表型相关得模块基因和核心基因但其步骤非常之多, 每次运行起来很是费劲, 但需要修改的参数并不多所以完全…

GitHub 热榜项目 - 日榜(2025-08-29)

GitHub 热榜项目 - 日榜(2025-08-29) 生成于&#xff1a;2025-08-29 统计摘要 共发现热门项目&#xff1a;11 个 榜单类型&#xff1a;日榜 本期热点趋势总结 本期GitHub热榜展现出三大技术趋势&#xff1a;1&#xff09;AI应用持续深化&#xff0c;ChatGPT等大模型系统提示…

【深度学习实战(58)】bash方式启动模型训练

export \PATHPYTHONPATH/workspace/mmlab/mmdetection/:/workspace/mmlab/mmsegmentation/:/workspace/mmlab/mmdeploy/:${env:PYTHONPATH} \CUDA_VISIBLE_DEVICES0 \DATA_ROOT_1/mnt/data/…/ \DATA_ROOT_2/mnt/data/…/ \DATA_ROOT_MASK/…/ \PATH_COMMON_PACKAGES_SO…sonoh…

【物联网】关于 GATT (Generic Attribute Profile)基本概念与三种操作(Read / Write / Notify)的理解

“BLE 读写”在这里具体指什么&#xff1f; 在你的系统里&#xff0c;树莓派是 BLE Central&#xff0c;Arduino 是 BLE Peripheral。 Central 和 Peripheral 通过 **GATT 特征&#xff08;Characteristic&#xff09;**交互&#xff1a;读&#xff08;Read&#xff09;&#x…

JavaSE丨集合框架入门(二):从 0 掌握 Set 集合

这节我们接着学习 Set 集合。一、Set 集合1.1 Set 概述java.util.Set 接口继承了 Collection 接口&#xff0c;是常用的一种集合类型。 相对于之前学习的List集合&#xff0c;Set集合特点如下&#xff1a;除了具有 Collection 集合的特点&#xff0c;还具有自己的一些特点&…

金属结构疲劳寿命预测与健康监测技术—— 融合能量法、红外热像技术与深度学习的前沿实践

理论基础与核心方法 疲劳经典理论及其瓶颈 1.1.疲劳失效的微观与宏观机理&#xff1a; 裂纹萌生、扩展与断裂的物理过程。 1.2.传统方法的回顾与评析。 1.3.引出核心问题&#xff1a;是否存在一个更具物理意义、能统一描述疲劳全过程&#xff08;萌生与扩展&#xff09;且试验量…

【贪心算法】day4

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的贪心算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

AI 与脑机接口的交叉融合:当机器 “读懂” 大脑信号,医疗将迎来哪些变革?

一、引言&#xff08;一&#xff09;AI 与脑机接口技术的发展现状AI 的崛起与广泛应用&#xff1a;近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术迅猛发展&#xff0c;已广泛渗透至各个领域。从图像识别、自然语言处理到智能决策系统&#xff0c;AI 展现出强大的数…

uniapp vue3 canvas实现手写签名

userSign.vue <template><view class"signature"><view class"btn-box" v-if"orientation abeam"><button click"clearClick">重签</button><button click"finish">完成签名</butt…

页面跳转html

实现流程结构搭建&#xff08;HTML&#xff09;创建侧边栏容器&#xff0c;通过列表或 div 元素定义导航项&#xff0c;每个项包含图标&#xff08;可使用字体图标库如 Font Awesome&#xff09;和文字&#xff0c;为后续点击交互预留事件触发点。样式设计&#xff08;CSS&…

Spring Boot自动装配机制的原理

文章目录一、自动装配的核心触发点&#xff1a;SpringBootApplication二、EnableAutoConfiguration的作用&#xff1a;导入自动配置类三、自动配置类的加载&#xff1a;SpringFactoriesLoader四、自动配置类的条件筛选&#xff1a;Conditional注解五、自动配置的完整流程六、自…

(未完结)阶段小总结(一)——大数据与Java

jdk8-21特性核心特征&#xff1a;&#xff08;8&#xff09;lambda&#xff0c;stream api&#xff0c;optional&#xff0c;方法引用&#xff0c;函数接口&#xff0c;默认方法&#xff0c;新时间Api&#xff0c;函数式接口&#xff0c;并行流&#xff0c;ComletableFuture。&…

嵌入式Linux驱动开发:设备树与平台设备驱动

嵌入式Linux驱动开发&#xff1a;设备树与平台设备驱动 引言 本笔记旨在详细记录嵌入式Linux驱动开发中设备树&#xff08;Device Tree&#xff09;和平台设备驱动&#xff08;Platform Driver&#xff09;的核心概念与实现。通过分析提供的代码与设备树文件&#xff0c;我们…

【完整源码+数据集+部署教程】骨折检测系统源码和数据集:改进yolo11-EfficientHead

背景意义 骨折作为一种常见的骨骼损伤&#xff0c;其诊断和治疗对患者的康复至关重要。传统的骨折检测方法主要依赖于医生的经验和影像学检查&#xff0c;如X光、CT等&#xff0c;这不仅耗时&#xff0c;而且容易受到主观因素的影响。随着计算机视觉和深度学习技术的迅猛发展&a…