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

目录

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); // 包含 versionproduct.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.pswp.cn/bicheng/82611.shtml

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

      相关文章

      成都鼎讯--通信信号模拟设备​

      在现代电磁通信领域&#xff0c;精准模拟复杂多变的信号环境&#xff0c;是提升通信装备性能与人员作战能力的核心命题。成都鼎讯以技术创新为驱动&#xff0c;凭借深厚的研发实力&#xff0c;重磅推出通信信号模拟设备&#xff0c;以前所未有的强大功能与卓越性能&#xff0c;…

      C# Windows Forms应用程序-003

      目录 项目结构 命名空间和类定义 主要控件 GroupBox 控件 Label 控件 TextBox 控件 Button 控件 OpenFileDialog 控件 方法说明 构造函数 Form1() Dispose(bool disposing) Main() InitializeComponent() button1_Click(object sender, System.EventArgs e) but…

      【C/C++】死锁的四大条件与预防策略详解

      文章目录 死锁的四大条件与预防策略详解一、死锁的产生条件&#xff08;四个必要条件&#xff09;二、代码示例三、死锁的预防手段&#xff08;以 C/C 为例&#xff09;1. 破坏“循环等待” —— 统一加锁顺序&#xff08;推荐&#xff09;2. 使用 std::lock 一次性加多个锁3. …

      Rust编程环境安装

      文章目录 Rust编程环境安装一、安装准备二、安装步骤对于Linux/macOS用户对于Windows用户 三、验证安装四、环境配置检查五、工具链管理六、附加功能七、常见问题处理八、编辑器支持九、其他 Rust编程环境安装 一、安装准备 1. 支持系统&#xff1a;Windows/Linux/macOS 2. 所…

      OpenHarmony平台驱动使用(五),HDMI

      OpenHarmony平台驱动使用&#xff08;五&#xff09; HDMI 概述 功能简介 HDMI&#xff08;High Definition Multimedia Interface&#xff09;&#xff0c;即高清多媒体接口&#xff0c;主要用于DVD、机顶盒等音视频Source到TV、显示器等Sink设备的传输。 HDMI以主从方式工…

      【Git】Commit Hash vs Change-Id

      文章目录 1、Commit 号2、Change-Id 号3、区别与联系4、实际场景示例5、为什么需要两者&#xff1f;6、总结附录——Gerrit 在 Git 和代码审查工具&#xff08;如 Gerrit&#xff09;中&#xff0c;Commit 号&#xff08;Commit Hash&#xff09; 和 Change-Id 号 是两个不同的…

      leetcode hot100刷题日记——21.不同路径

      和20题一样的思路link 题解&#xff1a; class Solution { public:int dfs(int i,int j,vector<vector<int>>&memo){//超过了边界&#xff0c;return 0if(i<0||j<0){return 0;}//从&#xff08;0&#xff0c;0&#xff09;到&#xff08;0&#xff0c;0…

      day2 MySQL表数据操作

      一&#xff1a;数据操作 注&#xff1a;在编写MySQL代码时可以不用区分大小写 1.查看表结构 desc 表名; -- 查看表中的字段类型&#xff0c;长度&#xff0c;约束。 2.字段的增加 AFTER table 表名 add 字段名 数据类型; -- 默认末尾添加 after table 表名 add 字段名 …

      GitAny - 無需登入的 GitHub 最新倉庫檢索工具

      地址&#xff1a;https://github.com/MartinxMax/gitany GitAny - 無需登入的 GitHub 專案搜尋工具 GitAny 是一款基於 Python 的工具&#xff0c;允許你在無需登入的情況下搜尋當天最新的 GitHub 專案。它支援模糊搜尋、條件篩選以及倉庫資料的視覺化分析。 安裝依賴 $ pip…

      格恩朗金属管浮子流量计 高精度测量的不二之选​

      在流量测量的复杂领域&#xff0c;精度就是生命线&#xff0c;直接关乎生产的稳定性、产品的质量以及资源的合理利用。大连格恩朗品牌的金属管浮子流量计&#xff0c;凭借其卓越的精度表现&#xff0c;成为各行业在流量测量时的最佳之选。​ 格恩朗金属管浮子流量计运用经典的可…

      【R语言编程绘图-箱线图】

      基本箱线图绘制 使用ggplot2绘制箱线图的核心函数是geom_boxplot()。以下是一个基础示例&#xff0c;展示如何用iris数据集绘制不同物种&#xff08;Species&#xff09;的萼片长度&#xff08;Sepal.Length&#xff09;分布&#xff1a; library(ggplot2) ggplot(iris, aes(…

      深度学习能取代机器学习吗?

      在人工智能领域&#xff0c;“机器学习”和“深度学习”这两个词经常被混为一谈。很多新手甚至以为只要跟 AI 有关的任务&#xff0c;都该用深度学习。但其实&#xff0c;它们并不是谁强谁弱的关系&#xff0c;而是适合不同场景的工具。 这篇文章就来帮你理清楚&#xff1a; 机…

      UPS的工作原理和UPS系统中旁路的作用

      UPS&#xff08;不间断电源&#xff09;根据工作原理和适用场景的不同&#xff0c;主要分为以下三种类型&#xff0c;每种类型的特点和适用场景如下&#xff1a; 1. 后备式UPS&#xff08;Offline/Standby UPS&#xff09; 工作原理&#xff1a; 正常供电时&#xff0c;负载直接…

      一级菜单401问题

      正常代码生成的前后台文件&#xff0c;菜单类型是一级标题&#xff0c; 菜单路径和前端组件的地址都正常写的:/projects/xxx/xxx/xxx/XxxList 其他生成的新列表都能点进去&#xff0c;只有这个点进去就是显示空白的像首页那个页面一样&#xff0c; 问题就出现在我第一次建这…

      ROS2 robot控制学习(一)

      controller_position.yaml使用说明 ROS 2 的 controller_manager 用途典型工作流程示例关键服务与话题扩展功能JointTrajectoryController 参数详解基本参数轨迹参数插值参数前馈控制代码示例动态参数调试参数ForwardCommandController 概述参数解释`joints``interface``allow_…

      LightGBM的python实现及参数优化

      文章目录 1. LightGBM模型参数介绍2. 核心优势3. python实现LightGBM3.1 基础实现3.1.1 Scikit-learn接口示例3.1.2 Python API示例 3.2 模型调优3.2.1 GridSearchCV简介3.2.2 LightGBM超参调优3.2.3 GridSearchCV寻优结果 在之前的文章 Boosting算法【AdaBoost、GBDT 、XGBoo…

      Map集合(双列集合)

      Map结合也称为“键值对集合”&#xff0c;格式&#xff1a;{key1value1&#xff0c;key2value2....} Map集合的特点&#xff1a; 键唯一&#xff1a;在Map集合中&#xff0c;键&#xff08;key&#xff09;是唯一的&#xff0c;不能有重复的键。如果尝试插入一个已经存在的键…

      springBoot项目测试时浏览器返回406问题解决方案

      1. 如果基于最新版本的SpringBoot官方骨架创建的SpringBoot项目&#xff0c;在勾选了lombok的依赖之后&#xff0c;会在pom.xml中引入如下两个插件&#xff1a; 2. 由于第一个插件 maven-compiler-plugin 的引入导致了这个问题&#xff0c;解决这个问题的方案呢&#xff0c;就是…

      21.享元模式:思考与解读

      原文地址:享元模式&#xff1a;思考与解读 更多内容请关注&#xff1a;深入思考与解读设计模式 引言 在软件开发中&#xff0c;特别是当你处理大量相似对象时&#xff0c;是否会遇到一个问题&#xff1a;大量的对象会占用大量的内存&#xff0c;而这些对象有许多相同的状态&…

      java方法重写学习笔记

      方法重写介绍 子类和父类有两个返回值&#xff0c;参数&#xff0c;名称都一样的方法&#xff0c; 子类的方法会覆盖父类的方法。 调用 public class Overide01 {public static void main(String[] args) {Dog dog new Dog();dog.cry();} }Animal类 public class Animal {…