You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

PostgreSQL+SpringBoot+Hibernate下实现80岁数据唯一限制的锁方案咨询

嘿,这个场景我之前在PostgreSQL+SpringBoot的项目里碰到过,咱们一步步捋清楚:

要不要给整张表加排他锁?绝对不需要!

完全没必要用整张表的排他锁——表锁会把所有对Man表的操作都卡住,并发性能拉胯到不行。我们只需要针对80岁这条特定记录做精准的锁控制,或者结合数据库的约束来实现,既安全又高效。

两种靠谱的实现方案

方案一:数据库层面的部分唯一索引(推荐,最可靠)

因为只有80岁需要唯一限制,其他年龄可以重复,所以不能直接给age字段加全局唯一约束。PostgreSQL支持部分唯一索引,刚好能解决这个需求:

首先在数据库里创建这个索引:

CREATE UNIQUE INDEX idx_man_age_80 ON man (age) WHERE age = 80;

这个索引只会对age=80的记录生效,确保这类记录最多有一条,其他年龄不受任何影响。

然后在SpringBoot的addMan方法里,直接插入并捕获唯一约束异常就行(数据库会帮我们拦截并发插入的冲突):

import jakarta.persistence.PersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

// 假设已注入EntityManager或Spring Data JPA的Repository
@Transactional
public void addMan(int age) {
    Logger log = LoggerFactory.getLogger(getClass());
    
    if (age == 80) {
        Man man = new Man();
        man.setAge(age);
        try {
            entityManager.persist(man); // 也可以用repository.save(man)
        } catch (PersistenceException e) {
            // 捕获唯一约束违反的异常
            if (e.getCause() instanceof java.sql.SQLIntegrityConstraintViolationException) {
                log.info("已经存在年龄为80岁的记录,无需重复添加");
                // 可根据业务需求选择忽略或抛出自定义异常
            } else {
                // 其他异常正常抛出
                throw e;
            }
        }
    } else {
        // 其他年龄直接插入,无限制
        Man man = new Man();
        man.setAge(age);
        entityManager.persist(man);
    }
}

这个方案的优势在于:数据库层面的约束是最底层的保障,哪怕应用层出问题,数据库也能拦住重复的80岁记录,而且并发性能极佳——索引只针对特定值,不会影响其他操作。

方案二:应用层行级悲观锁(适合不能修改数据库结构的场景)

如果因为权限或其他原因不能创建索引,那可以用Hibernate的悲观锁机制,只锁定可能存在的80岁记录,不会锁整张表:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.LockModeType;

@Transactional
public void addMan(int age) {
    Logger log = LoggerFactory.getLogger(getClass());
    
    if (age == 80) {
        // 用PESSIMISTIC_WRITE(排他锁)查询是否存在80岁的记录
        // 如果存在,会锁定这条行;如果不存在,后续插入时其他线程的相同查询会等待当前事务完成
        Man existingMan = entityManager.createQuery("SELECT m FROM Man m WHERE m.age = :age", Man.class)
                .setParameter("age", age)
                .setLockMode(LockModeType.PESSIMISTIC_WRITE)
                .getResultStream()
                .findFirst()
                .orElse(null);
        
        if (existingMan == null) {
            Man man = new Man();
            man.setAge(age);
            entityManager.persist(man);
        } else {
            log.info("年龄为80岁的记录已存在");
        }
    } else {
        Man man = new Man();
        man.setAge(age);
        entityManager.persist(man);
    }
}

这里要注意:这个方案依赖PostgreSQL默认的REPEATABLE READ隔离级别,确保事务内的查询结果一致。而且只会锁定80岁的那一行(如果存在),不会影响其他年龄的插入操作,性能比表锁好太多。

总结
  • 绝对不需要整张表的排他锁,完全是杀鸡用牛刀,会严重影响并发能力;
  • 优先选方案一,数据库层面的约束最可靠,性能最优;
  • 如果不能改数据库结构,再用方案二的行级悲观锁,精准控制锁范围。

内容的提问来源于stack exchange,提问作者Logic

火山引擎 最新活动