高并发环境下安全修改同一行数据的策略
随着互联网技术的飞速发展,越来越多的应用需要在高并发环境中运行,尤其是在处理大量用户请求时,数据库的并发控制成为了业务的关键。本文将介绍如何在高并发情况下,安全地修改数据库中的同一行数据。
1. 理解并发问题
并发操作的问题主要源于多个事务在同一时间尝试访问或修改同一行数据。这可能导致数据不一致,或者在极端情况下,导致数据丢失。为了防止这种情况,我们需要一种机制来确保在一个时间点,只有一个事务能修改特定的数据行。
2. 悲观锁
悲观锁是一种常见的并发控制技术,适用于数据竞争激烈的场景。当一个事务尝试读取或修改数据时,悲观锁会假设其他事务可能也会尝试修改该数据,因此会在数据上加锁。其它事务在此期间将不能对该数据进行修改,直到锁被释放。这种锁机制主要通过数据库提供的锁机制实现,如行锁、表锁等。
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
//关闭自动提交
conn.setAutoCommit(false);
//SELECT ... FOR UPDATE是悲观锁的典型应用,此时会锁住被选中的行,其他事务无法更新这些行
String sql = "SELECT * FROM product WHERE id = ? FOR UPDATE";
ps = conn.prepareStatement(sql);
ps.setInt(1, 1);
rs = ps.executeQuery();
if (rs.next()) {
//获取当前库存数量
int count = rs.getInt("count");
if (count > 0) {
String updateSql = "UPDATE product SET count = count - 1 WHERE id = ?";
ps = conn.prepareStatement(updateSql);
ps.setInt(1, 1);
ps.executeUpdate();
conn.commit();
System.out.println("扣减成功,剩余库存:" + (count - 1));
} else {
System.out.println("库存不足,扣减失败");
}
}
} catch (Exception e) {
if (conn != null) {
try {
//如果出现异常,进行回滚
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
} finally {
//在最后,关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3. 乐观锁
与悲观锁不同,乐观锁在数据被修改时并不加锁,而是在数据提交时检查数据是否被其他事务修改过。如果在一个事务处理过程中数据被另一个事务修改,那么这个事务就会回滚并重新尝试。乐观锁适用于数据竞争不激烈,读操作远多于写操作的场景。
在Java中,乐观锁常常通过"版本号"或者"时间戳"等方式实现。当读取数据时,同时也会读取数据的版本号,当更新数据时,会带上这个版本号,只有当数据库中的当前版本和带上的版本号相同,才会更新数据,并把版本号加1。
示例如下:首先,我们需要在实体类中添加一个版本号字段,并且用@Version
注解标注
import javax.persistence.*;
@Entity
@Data
public class Product {
@Id
@GeneratedValue
private Long id;
private Integer count;
//版本号,用于实现乐观锁
@Version
private Integer version;
}
然后,在进行更新操作时,JPA会自动带上版本号进行检查:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public void reduceCount(Long productId) {
//找到产品
Product product = productRepository.findById(productId).get();
//检查库存
if (product.getCount() > 0) {
//如果库存足够,减少库存
product.setCount(product.getCount() - 1);
productRepository.save(product);
} else {
//库存不足,抛出异常
throw new RuntimeException("库存不足");
}
}
}
4. MVCC(多版本并发控制)
多版本并发控制(MVCC)是一种用于增加数据库并发性能的技术,它会为每一行数据创建版本,以支持高并发读写。每当有新的事务尝试修改数据,它都会创建一个新的数据版本,而不是直接修改原始数据。这样可以让读事务继续访问旧版本数据,而写事务则修改新版本数据,从而提高并发性能。
5. 分布式锁
在分布式系统中,由于数据可能分散在多个节点上,我们需要一种全局的锁机制来保证数据一致性。分布式锁可以提供这样的机制,常见的实现方式有基于数据库的分布式锁,基于缓存(Redis等)的分布式锁,以及基于Zookeeper的分布式锁等。
// 使用 Redisson 库实现 Redis 分布式锁
RedissonClient redisson = Redisson.create();
RLock lock = redisson.getLock("myLock");
try{
lock.lock();
// 执行业务操作
}finally{
lock.unlock();
}
总的来说,在高并发环境下安全地修改同一行数据,我们需要选择合适的锁机制,以保证数据的一致性和系统的性能。同时,我们也需要关注系统的实际需求和性能瓶颈,以实现最优的并发控制策略。
转载自:https://juejin.cn/post/7239173848549605433