Mysql的乐观锁和悲观锁?

并发控制策略选择指南

以下是针对不同业务场景的并发控制策略选择方案,以及具体的实现示例和优化建议。

1. 基础方案选择

根据不同的业务场景选择合适的锁机制,可以有效平衡并发性和数据一致性。以下是常见业务场景及其推荐的并发控制方案:

业务场景 推荐方案 原因
用户个人资料更新 乐观锁 冲突概率低,用户体验好
库存扣减 悲观锁 避免超卖,保证数据准确性
订单状态变更 乐观锁 + 重试 平衡并发性和正确性
财务金额变更 悲观锁 必须保证数据绝对正确

1.1 乐观锁

乐观锁适用于冲突概率较低的场景,通过版本号或时间戳机制来检测数据是否被其他事务修改。

1.2 悲观锁

悲观锁适用于冲突概率较高或数据一致性要求严格的场景,通过数据库锁机制来防止数据被其他事务修改。

2. 乐观锁实现示例

2.1 实体类设计

使用 JPA 注解 @Version 来实现乐观锁:

public class Product {
    private Long id;
    private String name;
    private Integer stock;
    
    @Version
    private Integer version;
    
    // getters and setters
}

2.2 MyBatis 乐观锁更新

<update id="updateProduct">
    UPDATE products
    SET name = #{name},
        stock = #{stock},
        version = version + 1
    WHERE id = #{id}
    AND version = #{version}
</update>

2.3 服务层实现

@Transactional
public void updateProduct(Product product) {
    int rows = productMapper.updateProduct(product);
    if (rows == 0) {
        throw new OptimisticLockException("数据已被修改,请刷新后重试");
    }
}

3. 悲观锁实现示例

3.1 MyBatis 悲观锁查询

<select id="selectForUpdate" resultType="com.example.Product">
    SELECT * FROM products WHERE id = #{id} FOR UPDATE
</select>

3.2 服务层实现

@Transactional
public void deductStock(Long productId, int quantity) {
    // 1. 获取悲观锁
    Product product = productMapper.selectForUpdate(productId);
    
    // 2. 检查库存
    if (product.getStock() < quantity) {
        throw new RuntimeException("库存不足");
    }
    
    // 3. 更新库存
    product.setStock(product.getStock() - quantity);
    productMapper.update(product);
}

4. 混合策略实现

对于需要平衡并发和一致性的场景,可以先尝试乐观锁,如果发生冲突则转为悲观锁:

@Transactional
public void updateWithHybridLock(Product product) {
    // 先尝试乐观锁
    try {
        updateWithOptimisticLock(product);
    } catch (OptimisticLockException e) {
        // 冲突时转悲观锁
        Product lockedProduct = productMapper.selectForUpdate(product.getId());
        // 合并数据(根据业务规则)
        lockedProduct.setStock(product.getStock());
        productMapper.update(lockedProduct);
    }
}

5. 特殊场景处理

5.1 最后更新者优先

如果业务允许最后更新覆盖之前更新,可以不使用锁:

<update id="updateWithoutLock">
    UPDATE products
    SET name = #{name},
        stock = #{stock}
    WHERE id = #{id}
</update>

5.2 部分字段更新

只更新特定字段,减少冲突概率:

<update id="updateStockOnly">
    UPDATE products
    SET stock = #{stock}
    WHERE id = #{id}
</update>

6. 性能优化建议

6.1 缩短事务时间

尽可能减少锁持有时间,避免长时间占用锁资源。

6.2 减小锁粒度

锁定必要的数据而非整表,减少锁的竞争。

6.3 设置合理超时

避免长时间等待,设置合理的锁等待超时时间:

<select id="selectForUpdateWithTimeout" resultType="com.example.Product">
    SELECT * FROM products WHERE id = #{id} FOR UPDATE WAIT 3
</select>

6.4 考虑读写分离

将查询操作路由到只读副本,减轻主库的压力。

7. 事务设计原则

7.1 事务注解配置

@Transactional(
    isolation = Isolation.READ_COMMITTED,
    propagation = Propagation.REQUIRED,
    timeout = 30  // 秒
)
public void businessMethod() {
    // 业务逻辑
}

7.2 事务传播行为选择

  • REQUIRED:默认,加入当前事务。
  • REQUIRES_NEW:新建事务,适合独立操作。

8. 监控与排查

8.1 Oracle 锁监控查询

-- 查看当前锁情况
SELECT s.sid, s.serial#, s.username, s.osuser, 
       l.type, l.lmode, l.block, o.object_name
FROM v$session s, v$lock l, dba_objects o
WHERE s.sid = l.sid
  AND l.id1 = o.object_id(+)
ORDER BY l.block DESC, s.sid;

8.2 死锁处理

try {
    // 业务操作
} catch (DataAccessException e) {
    if (e.getCause() instanceof SQLException) {
        SQLException sqlEx = (SQLException) e.getCause();
        if (sqlEx.getErrorCode() == 60) {  // ORA-00060 死锁
            // 记录日志并重试
            log.warn("检测到死锁,准备重试");
            retryOperation();
        }
    }
}

总结

在实际项目中,建议根据业务需求和数据冲突概率选择合适的并发控制策略。通常情况下:

  • 80% 的场景使用乐观锁:适用于冲突概率较低的场景,提升用户体验。
  • 15% 的关键业务使用悲观锁:适用于数据一致性要求严格的场景。
  • 5% 的特殊场景使用混合策略:在乐观锁和悲观锁之间灵活切换,平衡并发性和正确性。

通过合理选择和应用并发控制策略,可以有效提升系统的并发性能和保证数据的一致性。

#mysql锁#
日常学习 文章被收录于专栏

记录日常学习

全部评论

相关推荐

07-17 13:09
已编辑
火炉中学 Java
本人27届菜鸡,实习入职四天了,我们组是开发一个教学平台,组长说暑假需求不多,让我慢慢熟悉项目。入职第一天是拿到了文档和一个项目的代码权限,把代码拉下来跑了起来,Maven下了一下午才把依赖下好。后面三天都在一直看代码,发现很多新东西,虽然项目框架基本上也就是我在黑马学的springboot和mabatis那一套,但是有很多别的东西或者一个人做项目没法接触到的点。我不知道我是不是对的,我感觉每天就是顺着前端发的请求看每个业务流程涉及到的东西,看到新东西我就去学,比如什么分布式id怎么生成的,excel怎么导入导出的,缓存级联删除逻辑什么的,我感觉看的很慢因为想搞懂没见过或者不熟悉的东西。我感觉这样下去一周都没法吃透这个项目,但组长人很好,跟我说刚实习多学点新东西,现在就慢慢看就行了,后面会给我发需求。我到底该怎么办,忽略细枝末节赶紧要需求吗,但是我感觉不学新东西很难做有亮点的需求,单做增删改查实习完了又不好包产出,我现在有点想把看懂的技术点包成自己的产出因为做的真的好厉害我自己肯定做不出来,唉我会的还是太少了,现在焦虑得不行,甚至后悔提前实习了,应该把分布式微服务学完再出来的😰我之前就只跟做了外卖和点评,背了一些juc&nbsp;jvm八股,微服务分布式都没学,真让我干活只能做增删改查吧,害怕组长骂我肺雾😭
火猴真君:评论区这么一致么 先把一些流程逻辑学通,记记笔记,不懂的问组长。 然后自己试着做 1,2 个功能跑跑看,来来回回几次,就慢慢熟悉业务代码了
点赞 评论 收藏
分享
07-16 18:03
门头沟学院 Java
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务