msql的乐观锁和幂等性问题解决方案

news2025/7/19 20:18:41

目录

1、介绍

2、乐观锁

2.1、核心思想

2.2、实现方式

1. 使用 version 字段(推荐)

2. 使用 timestamp 字段

2.3、如何处理冲突

2.4、乐观锁局限性

3、幂等性

3.1、什么是幂等性

3.2、乐观锁与幂等性的关系

1. 乐观锁如何辅助幂等性?

2. 乐观锁的局限性

3.3、如何设计

3.4、order_no 添加唯一约束

1、防止重复创建订单

2、保证业务逻辑的正确性

3、支持幂等性设计

4、节点故障场景

4.1. 数据库层面

1、事务的原子性

2、自动提交(Autocommit)

4.2. 应用层

1、重试机制

2、幂等性设计

3、异步消息队列

4.3. 网络恢复后的处理

1、客户端检测网络状态

2、服务端日志与监控


1、介绍

        在分布式系统中,乐观锁幂等性设计数据插入失败处理是保障数据一致性和系统可靠性的三大核心机制,它们共同协作以解决并发冲突、重复请求和网络异常等问题。

1.乐观锁
        通过在数据库中添加 version 或 timestamp 字段,确保并发更新时的数据一致性。每次更新时检查版本号是否匹配。

        若匹配则更新并递增版本号,否则抛出异常(如 StaleObjectStateException)。适用于读多写少的场景,减少锁竞争,但需业务层配合处理冲突重试。

2.幂等性设计
        确保同一请求多次执行的结果与一次执行相同,常用于支付、订单等关键业务。通过 唯一业务标识符(如订单号)请求ID数据库唯一约束 或 缓存记录 来拦截重复请求。例如,插入订单前先检查 order_no 是否已存在,若存在则直接返回结果,避免重复操作。

3.数据插入失败的处理

网络宕机

        若插入操作未提交,数据库事务会自动回滚;若已提交部分数据,需通过补偿机制(如回滚或修复)修正。

重试机制

        在网络恢复后,客户端可结合 指数退避算法 重试请求,但需确保重试操作是幂等的(如通过唯一约束或请求ID)。

异步队列

        将请求放入消息队列(如 Kafka、RabbitMQ),确保网络中断时消息不丢失,恢复后继续处理。

典型场景示例

        用户提交支付请求时,系统通过 order_no 的唯一约束防止重复订单,使用乐观锁避免并发修改价格,若网络中断则通过重试机制重新提交(但依赖幂等性设计避免重复扣款)。

核心目标

        通过 乐观锁 保证数据一致性,幂等性 防止重复操作,重试与补偿 应对网络异常,三者结合构建高可用、可靠的分布式系统。


2、乐观锁

          MySQL的乐观锁是一种并发控制机制,它假设数据冲突(多个事务同时修改同一数据)的概率较低,因此在读取数据时不加锁,而是在更新时检查数据是否被其他事务修改过。如果冲突发生,事务会失败并重试。

2.1、核心思想

  1. 读取数据时:记录数据的版本号(或时间戳)。
  2. 更新数据时:检查版本号是否一致,如果一致则更新,否则抛出异常(冲突)。

2.2、实现方式

在MySQL中,乐观锁通常通过以下方式实现:

1. 使用 version 字段(推荐)

  • 在表中添加一个 version 字段(整数类型),每次更新时自动递增。
  • 读取数据时获取当前 version 值。
  • 更新时将 version 作为条件,如果匹配则更新并递增 version

示例表结构

CREATE TABLE product (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    price DECIMAL(10,2),
    version INT DEFAULT 0  -- 乐观锁版本号
);

示例操作

1.读取数据

SELECT id, name, price, version FROM product WHERE id = 1;
-- 假设返回: id=1, name='Apple', price=10.00, version=5

2.更新数据(带版本号检查):

UPDATE product 
SET price = 12.00, version = version + 1 
WHERE id = 1 AND version = 5;

3.判断是否更新成功

            如果 version=5 的记录还存在,则更新成功。

            如果 version 已经被其他事务修改为 6,则更新失败(影响行数为0),此时需要抛出异常或重试。

    2. 使用 timestamp 字段

    • 类似 version 字段,但使用 TIMESTAMP 或 DATETIME 类型。
    • 每次更新时自动更新该字段。
    • 更新时检查 timestamp 是否匹配。

    1.读取数据

    SELECT id, name, price, update_time FROM product WHERE id = 1;
    -- 假设返回: id=1, name='Apple', price=10.00, update_time='2023-10-01 12:00:00'
    

    2.更新数据(带时间戳检查):

    UPDATE product 
    SET price = 12.00, update_time = NOW() 
    WHERE id = 1 AND update_time = '2023-10-01 12:00:00';
    

    3.判断是否更新成功

              如果 update_time 匹配,则更新成功。

              否则更新失败(影响行数为0)。

      2.3、如何处理冲突

      当乐观锁检测到冲突时(更新失败),应用程序需要:

      1. 抛出异常(如 StaleObjectStateException)。
      2. 重试逻辑:重新读取数据,重新尝试更新(可能需要限制重试次数)。

      代码示例(Java)

      int retryCount = 0;
      while (retryCount < MAX_RETRIES) {
          Product product = getProductFromDatabase(productId); // 包含 version
          product.setPrice(newPrice);
          int rowsUpdated = updateProductInDatabase(product); // 使用 version 条件更新
          if (rowsUpdated == 1) {
              break; // 更新成功
          } else {
              retryCount++;
              // 可能需要等待一段时间再重试
          }
      }
      if (retryCount >= MAX_RETRIES) {
          throw new RuntimeException("乐观锁重试失败");
      }
      

      小结:

              它适用于读多写少冲突概率低的场景,能有效提高并发性能,但需要业务层配合实现冲突处理逻辑。

      如下图所示:

      对比于悲观锁

      2.4、乐观锁局限性

      1. 需要业务层配合:必须显式实现版本号检查和重试逻辑。

      2. 无法完全避免冲突:在极端高并发下仍可能发生冲突。

      3. 不适合复杂事务:如果事务涉及多个表,乐观锁可能难以维护一致性。


      3、幂等性

      通过上面对于乐观锁的介绍,感觉是不是可以作为幂等性的处理手段呢?

              乐观锁可以作为处理幂等性问题的一种手段,但它的作用和适用范围需要结合具体场景来看。

      3.1、什么是幂等性

      幂等性(Idempotency)是指同一个操作多次执行的结果与执行一次的结果相同

      例如:

      • 发送重复的支付请求,不会导致重复扣款。
      • 提交重复的订单,不会生成多个订单。
      • 更新资源时,多次相同请求不会改变最终状态。

      幂等性设计的核心目标:防止因网络重传、用户重复点击、系统故障等原因导致的重复请求对业务逻辑产生副作用。

      3.2、乐观锁与幂等性的关系

              乐观锁(Optimistic Locking)主要用于解决并发更新时的数据一致性问题,而幂等性解决的是重复请求对业务逻辑的影响。两者的结合可以增强系统的健壮性。

      1. 乐观锁如何辅助幂等性?

              乐观锁通过 版本号(version)或时间戳(timestamp) 保证数据更新的原子性,防止并发冲突。

      在某些场景下,它可以间接支持幂等性:

      • 场景:更新某个资源时,重复的请求可能因版本号不匹配而失败,避免重复操作。
      • 示例:用户多次提交更新请求,若第一次请求已修改了数据版本号,后续重复请求会因版本号不一致而失败,从而避免重复操作。

      2. 乐观锁的局限性

              乐观锁无法直接解决幂等性问题,因为它不处理“重复请求”的识别和过滤。

      例如:

      • 如果用户多次提交相同的请求参数(如相同的订单号、交易号),乐观锁无法识别这是重复请求,只会检查版本号是否冲突。
      • 如果请求参数不同(如不同的版本号),乐观锁可能允许更新,但业务逻辑可能需要拒绝重复操作。

      3.3、如何设计

      在实际开发中,通常需要将乐观锁与其他幂等性策略结合使用,例如:

      1. 唯一业务标识符(Business Key)
      2. 请求ID(Request ID)
      3. 数据库唯一约束
      4. 缓存记录已处理的请求

      示例:支付接口的幂等性设计

      假设用户发起支付请求,接口需要确保同一笔订单不会被重复扣款:

      -- 表结构
      CREATE TABLE orders (
          id INT PRIMARY KEY,
          order_no VARCHAR(50) UNIQUE,  -- 唯一业务标识符
          amount DECIMAL(10,2),
          status VARCHAR(20),
          version INT DEFAULT 0  -- 乐观锁版本号
      );
      

      处理流程

      1. 客户端发送请求,包含 order_no 和 request_id(唯一请求ID)。
      2. 服务端处理
        • 检查缓存或数据库,是否存在已处理的 order_no 或 request_id
          • 如果存在,直接返回结果(幂等性保障)。
          • 如果不存在,继续处理。
        • 执行支付操作时,使用乐观锁更新订单状态:
      UPDATE orders 
      SET status = 'PAID', version = version + 1 
      WHERE id = ? AND version = ?;
      

      如果更新失败(版本号不匹配),说明订单状态已被其他事务修改,需重试或报错。

      关键点

      • 唯一业务标识符(order_no):直接过滤重复请求。
      • 请求ID(request_id):记录已处理的请求,避免重复消费。
      • 乐观锁(version):防止并发更新导致的数据不一致。

      3.4、order_no 添加唯一约束

      1、防止重复创建订单

              假设用户点击“提交订单”按钮多次,或网络重传导致相同请求被多次发送。如果没有唯一约束,可能会导致以下问题:

      • 重复插入订单:系统生成多个相同 order_no 的订单,浪费资源。
      • 业务逻辑混乱:例如,重复扣款、重复发货等。
      -- 假设没有唯一约束
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
      -- 用户重复提交相同订单号
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100); -- 会成功插入第二条数据!
      

      后果:系统会认为这是两个不同的订单,可能导致重复扣款、库存异常等问题。


      2、保证业务逻辑的正确性

      order_no 是业务的核心标识符,如果允许重复,会导致:

      • 数据不一致:无法通过 order_no 准确查询或修改订单。
      • 幂等性失效:重复请求无法被拦截,破坏系统的一致性。

      示例

      -- 有唯一约束后,第二次插入会失败
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100); -- 成功
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100); -- 报错:Duplicate entry
      

      3、支持幂等性设计

      唯一约束是实现幂等性的关键手段之一:

      • 幂等性:同一请求多次执行的结果与执行一次的结果相同。
      • 唯一约束:通过数据库层强制拦截重复请求,避免业务逻辑重复执行。

      示例

      -- 用户多次提交相同的订单号
      BEGIN TRANSACTION;
        -- 尝试插入订单
        INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
      COMMIT;
      
      -- 如果已经存在相同 order_no,会抛出异常,事务回滚,避免重复操作
      

      4、节点故障场景

              在插入数据的过程中如果发生网络宕机,处理方式取决于数据库的事务机制应用层的容错设计以及网络恢复后的重试策略

      以下是详细的分析和解决方案:

      4.1. 数据库层面

      1、事务的原子性

      • 如果插入操作被包裹在事务中(例如使用 BEGIN TRANSACTION 和 COMMIT),且数据库支持事务(如 MySQL 的 InnoDB 引擎):
        • 网络中断时:事务未提交,数据库会自动回滚未提交的更改。
        • 恢复后:需要重新发送插入请求。
        • 示例(MySQL)
      BEGIN;
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
      -- 网络中断,事务未提交,数据不会写入数据库
      

      2、自动提交(Autocommit)

      • 如果数据库处于自动提交模式(默认开启),每次插入操作会立即提交:
        • 网络中断时:可能已部分提交数据(如部分字段写入),导致数据不一致。
        • 解决方案:在应用层显式关闭自动提交,手动控制事务边界。

      4.2. 应用层

      1、重试机制

      • 重试逻辑:在网络恢复后,客户端可以重试插入请求。
        • 关键点:需确保重试操作是幂等的(见下文)。
      • 重试策略
        • 指数退避(Exponential Backoff):重试间隔逐渐增大(如 1s → 2s → 4s → ...),避免网络拥塞。
        • 最大重试次数限制:防止无限循环重试(如最多重试 3 次)。

      2、幂等性设计

      • 唯一约束:通过数据库的 UNIQUE 约束(如订单号 order_no)防止重复插入。
        • 示例:即使重试,只要 order_no 唯一,重复插入会失败,避免数据冗余。
      • 请求 ID(Request ID):为每个请求生成唯一 ID,记录已处理的请求。
        • 示例:在插入前检查请求 ID 是否已存在,若存在则直接返回结果。

      3、异步消息队列

      • 可靠性队列:将插入操作放入消息队列(如 Kafka、RabbitMQ),确保网络中断时消息不丢失。
        • 生产者:将插入请求发送到队列,即使网络中断,消息仍保留在队列中。
        • 消费者:网络恢复后,继续消费消息并执行插入操作。
        • 优点:解耦生产与消费,提高系统鲁棒性。

      4.3. 网络恢复后的处理

      1、客户端检测网络状态

      • 心跳机制:客户端定期检测与数据库的连接状态。
      • 自动重连:网络恢复后,客户端自动重新建立连接并重试未完成的请求。

      2、服务端日志与监控

      • 记录失败请求:在服务端记录失败的插入请求(如日志或数据库表),便于人工介入处理。
      • 告警通知:通过监控工具(如 Prometheus、Zabbix)检测异常,及时通知运维人员。

      总结:

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

      如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

      相关文章

      理解计算机系统_线程(九):线程安全问题

      前言 以<深入理解计算机系统>(以下称“本书”)内容为基础&#xff0c;对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定 引入 接续理解计算机系统_线程(八):并行-CSDN博客,内容包括12.7…

      vue3基本类型和对象类型的响应式数据

      vue3中基本类型和对象类型的响应式数据 OptionsAPI与CompstitionAPI的区别 OptionsAPI Options API • 特点&#xff1a;基于选项&#xff08;options&#xff09;来组织代码&#xff0c;将逻辑按照生命周期、数据、方法等分类。• 结构&#xff1a;代码按照 data 、 methods…

      3.8.4 利用RDD实现分组排行榜

      本实战任务通过Spark RDD实现学生成绩的分组排行榜。首先&#xff0c;准备包含学生成绩的原始数据文件&#xff0c;并将其上传至HDFS。接着&#xff0c;利用Spark的交互式环境或通过创建Maven项目的方式&#xff0c;读取HDFS中的成绩文件生成RDD。通过map操作将数据映射为二元组…

      python web flask专题-Flask入门指南:从安装到核心功能详解

      Flask入门指南&#xff1a;从安装到核心功能详解 Flask作为Python最流行的轻量级Web框架之一&#xff0c;以其简洁灵活的特性广受开发者喜爱。本文将带你从零开始学习Flask&#xff0c;涵盖安装配置、项目结构、应用实例、路由系统以及请求响应处理等核心知识点。 1. Flask安…

      【HW系列】—web组件漏洞(Strtus2和Apache Log4j2)

      本文仅用于技术研究&#xff0c;禁止用于非法用途。 文章目录 Struts2Struts2 框架介绍Struts2 历史漏洞汇总&#xff08;表格&#xff09;Struts2-045 漏洞详解 Log4j2Log4j2 框架介绍Log4j2 漏洞原理1. JNDI 注入2. 利用过程 Log4j2 历史漏洞JNDILDAP 反弹 Shell 流程 Strut…

      机器学习知识体系:从“找规律”到“做决策”的全过程解析

      你可能听说过“机器学习”&#xff0c;觉得它很神秘&#xff0c;像是让电脑自己学会做事。其实&#xff0c;机器学习的本质很简单&#xff1a;通过数据来自动建立规则&#xff0c;从而完成预测或决策任务。 这篇文章将用通俗的语言为你梳理机器学习的知识体系&#xff0c;帮助…

      STM32之FreeRTOS移植(重点)

      RTOS的基本概念 实时操作系统&#xff08;Real Time Operating System&#xff09;的简称就叫做RTOS&#xff0c;是指具有实时性、能支持实时控制系统工作的操作系统&#xff0c;RTOS的首要任务就是调度所有可以利用的资源来完成实时控制任务的工作&#xff0c;其次才是提高工…

      R语言科研编程-标准偏差柱状图

      生成随机数据 在R中&#xff0c;可以使用rnorm()生成正态分布的随机数据&#xff0c;并模拟分组数据。以下代码生成3组&#xff08;A、B、C&#xff09;随机数据&#xff0c;每组包含10个样本&#xff1a; set.seed(123) # 确保可重复性 group_A <- rnorm(10, mean50, sd…

      OpenGL Chan视频学习-11 Uniforms in OpenGL

      bilibili视频链接&#xff1a; 【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p5&vd_source44b77bde056381262ee55e448b9b1973 函数网站&#xff1a; docs.gl 说明&#xff1a; 1.之后就不再单独整理网站具体函数了&#xff0c;网站直接翻译…

      GitLab 从 17.10 到 18.0.1 的升级指南

      本文分享从 GitLab 中文本 17.10.0 升级到 18.0.1 的完整过程。 升级前提 查看当前安装实例的版本。有多种方式可以查看&#xff1a; 方式一&#xff1a; /help页面 可以直接在 /help页面查看当前实例的版本。以极狐GitLab SaaS 为例&#xff0c;在浏览器中输入 https://ji…

      产业集群间的专利合作关系

      需要准备的文件&#xff1a; 全国的专利表目标集群间的企业名单 根据专利的共同申请人&#xff0c;判断这两家企业之间存在专利合作关系。 利用1_filter_patent.py&#xff0c;从全国的3000多万条专利信息中&#xff0c;筛选出与目标集群企业相关的专利。 只要专利的申请人包…

      KT6368A通过蓝牙芯片获取手机时间详细说明,对应串口指令举例

      一、功能简介 KT6368A双模蓝牙芯片支持连接手机&#xff0c;获取手机的日期、时间信息&#xff0c;可以同步RTC时钟 1、无需安装任何app&#xff0c;直接使用系统蓝牙即可实现 2、同时它不影响音频蓝牙&#xff0c;还支持一些简单的AT指令进行操作 3、实现的方式&#xff1…

      计算机网络实验课(二)——抓取网络数据包,并实现根据条件过滤抓取的以太网帧,分析帧结构

      文章目录 一、添加控件二、代码分析2.1 代码2.2 控件初始化2.3 打开和关闭设备2.4 开始和结束捕获2.5 设置捕获条件2.6 捕获数据包 三、运行程序四、结果分析 提要&#xff1a;如果你通过vs打开.sln文件&#xff0c;然后代码界面或者前端界面都没找到&#xff0c;视图里面也没找…

      78. Subsets和90. Subsets II

      目录 78.子集 方法一、迭代法实现子集枚举 方法二、递归法实现子集枚举 方法三、根据子集元素个数分情况收集 方法四、直接回溯法 90.子集二 方法一、迭代法实现子集枚举 方法二、递归法实现子集枚举 方法三、根据子集元素个数分情况收集 方法四、直接回溯法 78.子集…

      ElasticSearch整合SpringBoot

      ElasticSearch 整合SpringBoot ES官方提供了各种不同语言的客户端。用来操作ES。这些客户端的本质就是组装DSL语句&#xff0c;通过HTTP请求发送给ES。 设计索引库 跟据数据库的表结构进行ES索引库的创建时。如果字段需要进行倒排索引的时候请为它指定分词器。如果该字段不是…

      2025上半年软考高级系统架构设计师经验分享

      笔者背景 笔者在成都工作近7年&#xff0c; 一直担任研发大头兵&#xff0c;平日工作主要涵盖应用开发&#xff08;Java&#xff09;与数仓开发&#xff0c;对主流数据库、框架等均有涉猎&#xff0c;但谈不上精通。 最近有一些职业上的想法&#xff0c;了解到软考有那么一丁点…

      uni-app学习笔记十二-vue3中创建组件

      通过组件&#xff0c;可以很方便地实现页面复用&#xff0c;减少重复页面的创建&#xff0c;减少重复代码。一个页面可以引入多个组件。下面介绍在HBuilder X中创建组件的方法&#xff1a; 一.组件的创建 1.选中项目&#xff0c;右键-->新建目录(文件夹)&#xff0c;并将文…

      一键启动多个 Chrome 实例并自动清理的 Bash 脚本分享!

      目录 一、&#x1f4e6; 脚本功能概览 二、&#x1f4dc; 脚本代码一览 三、&#x1f50d; 脚本功能说明 &#xff08;一&#xff09;✅ 支持批量启动多个 Chrome 实例 &#xff08;二&#xff09;✅ 每个实例使用独立用户数据目录 &#xff08;三&#xff09;✅ 启动后自…

      4 月 62100 款 App 被谷歌下架!环比增长 28%

      大家好&#xff0c;我是牢鹅&#xff01;上周刚刚结束的 2025 年 Google I/O 开发者大会&#xff0c; Google Play 带来了一系列的更新&#xff0c;主要围绕提升优质 App 的"发现"、"互动"和"收入"三大核心内容。 这或许正是谷歌生态的一个侧影…

      mediapipe标注视频姿态关键点(基础版加进阶版)

      前言 手语视频流的识别有两种大的分类&#xff0c;一种是直接将视频输入进网络&#xff0c;一种是识别了关键点之后再进入网络。所以这篇文章我就要来讲讲如何用mediapipe对手语视频进行关键点标注。 代码 需要直接使用代码的&#xff0c;我就放这里了。环境自己配置一下吧&…