Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql 乐观锁和幂等性

MySql的乐观锁和幂等性问题解决方案及场景示例

作者:找不到、了

在分布式系统中,乐观锁、幂等性设计和数据插入失败处理是保障数据一致性和系统可靠性的三大核心机制,它们共同协作以解决并发冲突、重复请求和网络异常等问题,这篇文章主要介绍了MySql的乐观锁和幂等性问题解决方案及场景示例,需要的朋友可以参考下

1、介绍

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

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

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

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

3.数据插入失败的处理

网络宕机

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

重试机制

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

异步队列

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

典型场景示例

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

核心目标

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

2、乐观锁

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

2.1、核心思想

2.2、实现方式

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

1. 使用 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字段

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、如何处理冲突

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

代码示例(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、乐观锁局限性

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

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

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

3、幂等性

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

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

3.1、什么是幂等性

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

例如:

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

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

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

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

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

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

2. 乐观锁的局限性

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

例如:

3.3、如何设计

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

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

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

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

处理流程

UPDATE orders 
SET status = 'PAID', version = version + 1 
WHERE id = ? AND version = ?;

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

关键点

3.4、order_no 添加唯一约束

1、防止重复创建订单

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

-- 假设没有唯一约束
INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
-- 用户重复提交相同订单号
INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100); -- 会成功插入第二条数据!

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

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

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 引擎):

BEGIN;
INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
-- 网络中断,事务未提交,数据不会写入数据库

2、自动提交(Autocommit)

4.2. 应用层

4.3. 网络恢复后的处理

1、客户端检测网络状态

2、服务端日志与监控

总结:

您可能感兴趣的文章:
阅读全文