高并发环境下安全修改同一行数据库数据的策略分享

 更新时间:2023年06月01日 10:00:56   作者:SJT  
随着互联网技术的发展,越来越多的应用需要在高并发环境中运行,数据库的并发控制成为了业务的关键,本文将介绍如何在高并发情况下,安全地修改数据库中的同一行数据,需要的可以参考一下

随着互联网技术的飞速发展,越来越多的应用需要在高并发环境中运行,尤其是在处理大量用户请求时,数据库的并发控制成为了业务的关键。本文将介绍如何在高并发情况下,安全地修改数据库中的同一行数据。

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();
}

总的来说,在高并发环境下安全地修改同一行数据,我们需要选择合适的锁机制,以保证数据的一致性和系统的性能。同时,我们也需要关注系统的实际需求和性能瓶颈,以实现最优的并发控制策略。

到此这篇关于高并发环境下安全修改同一行数据库数据的策略分享的文章就介绍到这了,更多相关修改数据库数据内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java基础之StringBuffer详解

    Java基础之StringBuffer详解

    这篇文章主要介绍了Java基础之StringBuffer详解,文中有非常详细的代码示例,对正在学习java基础的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • SpringCloud Gateway使用详解

    SpringCloud Gateway使用详解

    Spring Cloud Gateway是一个基于Spring Boot 2.x和Spring WebFlux的API网关,可以帮助我们构建微服务架构中的统一入口。感兴趣的同学可以参考一下
    2023-04-04
  • Hadoop Combiner使用方法详解

    Hadoop Combiner使用方法详解

    这篇文章主要介绍了 Hadoop Combiner使用方法详解的相关资料,希望通过本文能帮助到大家让大家理解掌握这部分内容,需要的朋友可以参考下
    2017-10-10
  • javax NotBlank和Email注解失效的解决

    javax NotBlank和Email注解失效的解决

    这篇文章主要介绍了javax NotBlank和Email注解失效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Spring Data JPA的Audit功能审计数据库的变更

    Spring Data JPA的Audit功能审计数据库的变更

    数据库审计是指当数据库有记录变更时,可以记录数据库的变更时间和变更人等,这样以后出问题回溯问责也比较方便,本文讨论Spring Data JPA审计数据库变更问题,感兴趣的朋友一起看看吧
    2021-06-06
  • Mybatis中like搭配concat的写法详解

    Mybatis中like搭配concat的写法详解

    这篇文章主要介绍了Mybatis中like搭配concat的写法详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • SpringBoot 整合jdbc和mybatis的方法

    SpringBoot 整合jdbc和mybatis的方法

    该文章主要为记录如何在SpringBoot项目中整合JDBC和MyBatis,在整合中我会使用简单的用法和测试用例,感兴趣的朋友跟随小编一起看看吧
    2019-11-11
  • TCP/IP协议中三次握手四次挥手的原理及流程分析

    TCP/IP协议中三次握手四次挥手的原理及流程分析

    这篇文章主要介绍了TCP/IP协议中三次握手四次挥手的原理及流程分析,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Java使用System.currentTimeMillis()方法计算程序运行时间的示例代码

    Java使用System.currentTimeMillis()方法计算程序运行时间的示例代码

    System.currentTimeMillis() 方法的返回类型为 long ,表示毫秒为单位的当前时间,文中通过示例代码介绍了计算 String 类型与 StringBuilder 类型拼接字符串的耗时情况,对Java计算程序运行时间相关知识感兴趣的朋友一起看看吧
    2022-03-03
  • spring boot tomcat jdbc pool的属性绑定

    spring boot tomcat jdbc pool的属性绑定

    这篇文章主要介绍了spring boot tomcat jdbc pool的属性绑定的相关资料,非常不错,具有参考借鉴价值,需要的朋友参考下
    2018-01-01

最新评论