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锁#
日常学习 文章被收录于专栏

记录日常学习

全部评论

相关推荐

06-30 22:43
门头沟学院 Java
终于来到第三期了,这位同学已经把他的教育经历给截屏出去了,所以就没有需要打码的地方先看获奖情况,大部分同学是把这些放在教育经历里面的,也是可以的,有三个挺不错的奖项,还有一个英语四级,这里已经很够了,acm那个我觉得可以整句话加粗,这个完全可以特别自信然后看专业技能部分,也很好用,补充的内容很少,ioc,aop,还有一些小而细的知识点,可以参考别人的简历,多补充一些,但是不要再多出来一行就行,最后面那个云原生和深度学习,我觉得有些同学可能觉得这些不用写,毕竟是JAVA简历,但是我要告诉大家一个可能不知道的事情,现在很多编程都是面向AI和云的编程了,最后这两点实际上是非常加分的东西,并且通过他写的内容可以看出来他很熟悉,如果在面试的时候表现再良好的话,那专业技能这里就是非常非常加分的,很少有同学能在专业技能这里和别人拉开差距然后看项目经历部分,这里对比专业技能部分就反差太大了,因为这里写的有些糟糕,虽然我不太清楚你学什么的,但看这个项目能看出来,你在JAVA上没下太大功夫,主修的更像是人工智能方面,这在JAVA简历上比较扣分,而且项目的描述一般都是5到6点,其中前两三点是含金量很高的描述,但是你这个项目很多描述连半行都没有,这肯定是不行的,你要自己改一改,包装一下,比如商城项目第一条描述,你这么改:用xx实现多级缓存方案减少了主页和商品详情页中大部分信息的响应时间从x毫秒减少到x毫秒。这样描述内容就会饱满,并且听起来也要更厉害,像这样套公式简单包装一下,面试绝对更多下面的内容是我个人的,如果你最近在找暑期实习或者在找工作的话,又因为网上找的项目太烂,大街或者含金量不高,实在没什么好包装的,我有大厂最近做过的实习项目,也是可以包装到简历中的,感兴趣可以看简介中的项目地址,保证100%祝你更上一层楼
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

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