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、核心思想
- 读取数据时:记录数据的版本号(或时间戳)。
- 更新数据时:检查版本号是否一致,如果一致则更新,否则抛出异常(冲突)。
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、如何处理冲突
当乐观锁检测到冲突时(更新失败),应用程序需要:
- 抛出异常(如
StaleObjectStateException
)。 - 重试逻辑:重新读取数据,重新尝试更新(可能需要限制重试次数)。
代码示例(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、乐观锁局限性
-
需要业务层配合:必须显式实现版本号检查和重试逻辑。
-
无法完全避免冲突:在极端高并发下仍可能发生冲突。
-
不适合复杂事务:如果事务涉及多个表,乐观锁可能难以维护一致性。
3、幂等性
通过上面对于乐观锁的介绍,感觉是不是可以作为幂等性的处理手段呢?
乐观锁可以作为处理幂等性问题的一种手段,但它的作用和适用范围需要结合具体场景来看。
3.1、什么是幂等性
幂等性(Idempotency)是指同一个操作多次执行的结果与执行一次的结果相同。
例如:
- 发送重复的支付请求,不会导致重复扣款。
- 提交重复的订单,不会生成多个订单。
- 更新资源时,多次相同请求不会改变最终状态。
幂等性设计的核心目标:防止因网络重传、用户重复点击、系统故障等原因导致的重复请求对业务逻辑产生副作用。
3.2、乐观锁与幂等性的关系
乐观锁(Optimistic Locking)主要用于解决并发更新时的数据一致性问题,而幂等性解决的是重复请求对业务逻辑的影响。两者的结合可以增强系统的健壮性。
1. 乐观锁如何辅助幂等性?
乐观锁通过 版本号(version)或时间戳(timestamp) 保证数据更新的原子性,防止并发冲突。
在某些场景下,它可以间接支持幂等性:
- 场景:更新某个资源时,重复的请求可能因版本号不匹配而失败,避免重复操作。
- 示例:用户多次提交更新请求,若第一次请求已修改了数据版本号,后续重复请求会因版本号不一致而失败,从而避免重复操作。
2. 乐观锁的局限性
乐观锁无法直接解决幂等性问题,因为它不处理“重复请求”的识别和过滤。
例如:
- 如果用户多次提交相同的请求参数(如相同的订单号、交易号),乐观锁无法识别这是重复请求,只会检查版本号是否冲突。
- 如果请求参数不同(如不同的版本号),乐观锁可能允许更新,但业务逻辑可能需要拒绝重复操作。
3.3、如何设计
在实际开发中,通常需要将乐观锁与其他幂等性策略结合使用,例如:
- 唯一业务标识符(Business Key)
- 请求ID(Request ID)
- 数据库唯一约束
- 缓存记录已处理的请求
示例:支付接口的幂等性设计
假设用户发起支付请求,接口需要确保同一笔订单不会被重复扣款:
-- 表结构
CREATE TABLE orders (id INT PRIMARY KEY,order_no VARCHAR(50) UNIQUE, -- 唯一业务标识符amount DECIMAL(10,2),status VARCHAR(20),version INT DEFAULT 0 -- 乐观锁版本号
);
处理流程:
- 客户端发送请求,包含
order_no
和request_id
(唯一请求ID)。 - 服务端处理:
- 检查缓存或数据库,是否存在已处理的
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)检测异常,及时通知运维人员。
总结:
相关文章:

msql的乐观锁和幂等性问题解决方案
目录 1、介绍 2、乐观锁 2.1、核心思想 2.2、实现方式 1. 使用 version 字段(推荐) 2. 使用 timestamp 字段 2.3、如何处理冲突 2.4、乐观锁局限性 3、幂等性 3.1、什么是幂等性 3.2、乐观锁与幂等性的关系 1. 乐观锁如何辅助幂等性…...
Python 实现桶排序详解
1. 核心原理 桶排序是一种非比较型排序算法,通过将数据分配到多个“桶”中,每个桶单独排序后再合并。其核心步骤包括: 分桶:根据元素的范围或分布,将数据分配到有限数量的桶中。桶内排序:对每个非空桶内的…...
大模型(5)——编码器(Encoder)、解码器(Decoder)
文章目录 一、编码器(Encoder)1. 核心作用2. 典型结构(以Transformer为例)3. 应用场景 二、解码器(Decoder)1. 核心作用2. 典型结构(以Transformer为例)3. 应用场景 三、编码器与解码…...
Web3怎么本地测试连接以太坊?
ETHEREUM_RPC_URLhttps://sepolia.infura.io/v3/你的_INFURA_API_KEY 如果你没有 Infura Key,注册 Infura 或 Alchemy,拿一个免费测试网节点就行: Infura:https://infura.io Alchemy:Alchemy - the web3 developme…...
Vue-02 (使用不同的 Vue CLI 插件)
使用不同的 Vue CLI 插件 Vue CLI 插件扩展了 Vue 项目的功能,让你可以轻松集成 TypeScript、Vuex、路由等功能。它们可以自动进行配置和设置,从而节省您的时间和精力。了解如何使用这些插件对于高效的 Vue 开发至关重要。 了解 Vue CLI 插件 Vue CLI…...
理解vue-cli 中进行构建优化
在 Vue CLI 项目中进行构建优化,是前端性能提升的重要手段。它涉及到 Webpack 配置、代码分包、懒加载、依赖优化、图片压缩等多个方面。 🧱 基础构建优化 设置生产环境变量 NODE_ENVproduction Vue CLI 会自动在 npm run build 时开启以下优化&…...

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

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

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

python web flask专题-Flask入门指南:从安装到核心功能详解
Flask入门指南:从安装到核心功能详解 Flask作为Python最流行的轻量级Web框架之一,以其简洁灵活的特性广受开发者喜爱。本文将带你从零开始学习Flask,涵盖安装配置、项目结构、应用实例、路由系统以及请求响应处理等核心知识点。 1. Flask安…...
C语言中的“类框架”工具
C语言中的“框架”:库与轻量级工具生态解析 一、C语言的设计哲学与框架定位 C语言作为一门系统级编程语言,核心目标是提供高效、灵活的底层控制能力。与Java、Python等高级语言不同,C语言本身不内置全栈框架…...

【HW系列】—web组件漏洞(Strtus2和Apache Log4j2)
本文仅用于技术研究,禁止用于非法用途。 文章目录 Struts2Struts2 框架介绍Struts2 历史漏洞汇总(表格)Struts2-045 漏洞详解 Log4j2Log4j2 框架介绍Log4j2 漏洞原理1. JNDI 注入2. 利用过程 Log4j2 历史漏洞JNDILDAP 反弹 Shell 流程 Strut…...
第六十八篇 从“超市收银系统崩溃”看JVM性能监控与故障定位实战
目录 引言:当技术问题遇上生活场景一、JVM的“超市货架管理哲学”二、收银员工具箱:JVM监控三板斧三、典型故障诊断实录四、防患于未然的运维智慧五、结语:从故障救火到体系化防控 引言:当技术问题遇上生活场景 想象一个周末的傍…...
Debian 11 之使用hostapd与dnsmasq进行AP设置
目录 1: 安装必要的软件2: 配置dnsmasq3: 配置 hostapd4: 配置网络接口5: 启动服务总结 在Debian 11(也称为Bullseye)下设置热点,你可以使用多种方法,但最常见和简单的方法之一是使用hostapd工具配合dnsmasq。这种方法不需要额外的…...
有铜半孔的设计规范与材料创新
设计关键参数 孔径与间距限制 最小孔径需≥0.6mm,孔边距≥0.5mm,避免铜层脱落;拼版时半孔区域需预留2mm间距防止撕裂。 阻焊桥设计 必须保留阻焊桥(宽度≥0.1mm),防止焊锡流入孔内造成短路。 猎板的材料…...

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

STM32之FreeRTOS移植(重点)
RTOS的基本概念 实时操作系统(Real Time Operating System)的简称就叫做RTOS,是指具有实时性、能支持实时控制系统工作的操作系统,RTOS的首要任务就是调度所有可以利用的资源来完成实时控制任务的工作,其次才是提高工…...
做好测试用例设计工作的关键是什么?
测试用例设计是软件测试的核心环节,好的测试用例能高效发现缺陷,差的测试用例则可能漏测关键问题。结合多年测试经验,我认为做好测试用例设计的关键在于以下6点: 1. 深入理解需求(核心基础) ✅ 关键点: 与产品经理/开发对齐,确保理解无偏差(避免“我以为”式测试) 拆…...

R语言科研编程-标准偏差柱状图
生成随机数据 在R中,可以使用rnorm()生成正态分布的随机数据,并模拟分组数据。以下代码生成3组(A、B、C)随机数据,每组包含10个样本: set.seed(123) # 确保可重复性 group_A <- rnorm(10, mean50, sd…...
未来教育考试答题软件4.0【自用链接备份】
未来教育考试答题软件4.0【自用链接备份】 http://www.downyi.com/downinfo/240413.html 补丁地址:https://www.wodown.com/soft/43108.html...

OpenGL Chan视频学习-11 Uniforms in OpenGL
bilibili视频链接: 【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p5&vd_source44b77bde056381262ee55e448b9b1973 函数网站: docs.gl 说明: 1.之后就不再单独整理网站具体函数了,网站直接翻译…...
Flink系列文章列表
把写的文章做一个汇总,会陆续更新的。 Flink流处理原理与实践:状态管理、窗口操作与容错机制-CSDN博客...

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

产业集群间的专利合作关系
需要准备的文件: 全国的专利表目标集群间的企业名单 根据专利的共同申请人,判断这两家企业之间存在专利合作关系。 利用1_filter_patent.py,从全国的3000多万条专利信息中,筛选出与目标集群企业相关的专利。 只要专利的申请人包…...
PyQt学习系列02-模型-视图架构与数据管理
PyQt学习系列笔记(Python Qt框架) 第二课:PyQt的模型-视图架构与数据管理 一、模型-视图架构概述 1.1 什么是模型-视图架构? 模型-视图(Model-View)是Qt框架中用于数据展示和交互的核心设计模式。它将数…...
redis主从复制架构安装与部署
redis主从复制架构安装与部署 1、Redis 一主两从架构的优势2、环境准备3、下载redis4、解压缩文件5、编辑配置文件6、创建数据目录并启动Redis7、检查主从状态8、 Redis Sentinel 模式 1、Redis 一主两从架构的优势 Redis 采用一主两从(1个主节点 2个从节点&#…...
Kotlin 中 Lambda 表达式的语法结构及简化推导
在 Kotlin 编程中,Lambda 表达式是一项非常实用且强大的功能。今天,我们就来深入探讨一下 Lambda 表达式的语法结构,以及它那些令人 “又爱又恨” 的简化写法。 一、Lambda 表达式完整语法结构 Lambda 表达式最完整的语法结构定义为{参数名…...
YOLOv2 深度解析:目标检测领域的进阶之路
在计算机视觉领域,目标检测一直是研究和应用的热点方向。YOLO(You Only Look Once)系列算法以其快速高效的特点,在目标检测领域占据了重要地位。YOLOv2 作为 YOLO 系列算法的重要迭代版本,在 YOLOv1 的基础上进行了诸多…...

KT6368A通过蓝牙芯片获取手机时间详细说明,对应串口指令举例
一、功能简介 KT6368A双模蓝牙芯片支持连接手机,获取手机的日期、时间信息,可以同步RTC时钟 1、无需安装任何app,直接使用系统蓝牙即可实现 2、同时它不影响音频蓝牙,还支持一些简单的AT指令进行操作 3、实现的方式࿱…...

计算机网络实验课(二)——抓取网络数据包,并实现根据条件过滤抓取的以太网帧,分析帧结构
文章目录 一、添加控件二、代码分析2.1 代码2.2 控件初始化2.3 打开和关闭设备2.4 开始和结束捕获2.5 设置捕获条件2.6 捕获数据包 三、运行程序四、结果分析 提要:如果你通过vs打开.sln文件,然后代码界面或者前端界面都没找到,视图里面也没找…...