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