与 SQL 相比,Hibernate JPQL 查询非常慢

Posted

技术标签:

【中文标题】与 SQL 相比,Hibernate JPQL 查询非常慢【英文标题】:Hibernate JPQL query is very slow compared to SQL 【发布时间】:2021-01-12 15:46:09 【问题描述】:

在我的项目中,我遇到了一个需要大约 1.5 秒的 JPQL 查询的问题。当我直接在 PostgreSQL 数据库上执行从调试日志(执行 Hibernates 的同一日志)复制的 SQL 查询时,大约需要 15 毫秒。

@Service
@Transactional
@Slf4j
public class PersonSyncServiceImpl
        extends HotelApiCommunicationService implements PersonSyncService 
[...]
    private PersonLinked getLinkedPerson(CNPerson cnPerson, Obiekt obiekt) 
        PersonLinked person = cnPerson.getPersonLinked();
        if (person == null)      
            var o = cnPerson;
            List<PersonLinked> personList = personLinkedDao.findPersonLinkedByRecordData(
                    o.getPersonImiona(), o.getPersonNazwisko(), o.getPersonPesel(), o.getPersonEmail(), o.getPersonTelefon1());            
            person = personList.stream()                
                    .findFirst().orElse(null);
            if (person == null) 
                person = createPersonLinkedFromCnPerson(cnPerson);
                personLinkedDao.save(person);
            
            cnPerson.setPersonLinked(person);            
        
        return person;
    
[...]   
   

问题出在这一行:

List<PersonLinked> personList = personLinkedDao.findPersonLinkedByRecordData(
        o.getPersonImiona(), o.getPersonNazwisko(), o.getPersonPesel(), o.getPersonEmail(), o.getPersonTelefon1());  

带有已定义查询的道:

@Repository
@Transactional
public interface PersonLinkedDao extends JpaRepository<PersonLinked, Long> 

    @Query("select o from PersonLinked o \n" +
            "where o.personImiona = :imie and o.personNazwisko = :nazwisko \n" +
            "  and (o.personPesel = :pesel or o.personEmail = :email or o.personTelefon1 = :telefon)")
    List<PersonLinked> findPersonLinkedByRecordData(
                                                  @Param("imie") String personImiona,
                                                  @Param("nazwisko") String personNazwisko,
                                                  @Param("pesel") String personPesel,
                                                  @Param("email") String personEmail,
                                                  @Param("telefon") String personTelefon);

来自 Hibernate 调试日志的 SQL:

select [..]
from
    person personlinke0_ 
where
    personlinke0_.person_imiona=? 
    and personlinke0_.person_nazwisko=? 
    and (
        personlinke0_.person_pesel=? 
        or personlinke0_.person_email=? 
        or personlinke0_.person_telefon1=?
    )

当我在数据库上执行这个查询大约需要 15 毫秒,从代码执行大约需要 1.5 秒。我在代码中注释掉了这一行并且滞后消失了,所以肯定问题是这个 jpql 选择。

数据库连接配置:

spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect
spring.datasource.url=jdbc:postgresql://192.168.1.200:5433/XXXXXXX
spring.datasource.username=XXXXX
spring.datasource.password=XXXXX
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.generate_statistics=true

更新 1:

调试日志:

26-09-2020 16:06:36.130 [http-nio-8091-exec-2] DEBUG org.hibernate.SQL.logStatement - 
    select [...]
    from
        person personlinke0_ 
    where
        personlinke0_.person_imiona=? 
        and personlinke0_.person_nazwisko=? 
        and (
            personlinke0_.person_pesel=? 
            or personlinke0_.person_email=? 
            or personlinke0_.person_telefon1=?
        )
26-09-2020 16:06:36.130 [http-nio-8091-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager.doGetTransaction - Found thread-bound EntityManager [SessionImpl(1971671100<open>)] for JPA transaction
26-09-2020 16:06:36.130 [http-nio-8091-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager.handleExistingTransaction - Participating in existing transaction
26-09-2020 16:06:36.146 [http-nio-8091-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager.doGetTransaction - Found thread-bound EntityManager [SessionImpl(1971671100<open>)] for JPA transaction
26-09-2020 16:06:36.146 [http-nio-8091-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager.handleExistingTransaction - Participating in existing transaction
26-09-2020 16:06:36.146 [http-nio-8091-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager.doGetTransaction - Found thread-bound EntityManager [SessionImpl(1971671100<open>)] for JPA transaction
26-09-2020 16:06:36.146 [http-nio-8091-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager.handleExistingTransaction - Participating in existing transaction
26-09-2020 16:06:37.521 [http-nio-8091-exec-2] DEBUG org.hibernate.SQL.logStatement - 

更新 2:

PersonLinked 实体类:

@Entity
@Table(name = "person")
@Getter
@Setter
@SuperBuilder
@EqualsAndHashCode(of = "personId")
public class PersonLinked extends SCPerson 

    @Id
    @GeneratedValue(generator = "seq_person", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "seq_person", sequenceName = "seq_person", allocationSize = 30)
    @Column(name = "OSOBA_ID", nullable = false)
    private Long personId;


    @OneToMany(mappedBy = "personLinked", fetch = FetchType.LAZY)
    private List<CNPerson> cnPersonList;

    @Tolerate
    public PersonLinked() 
        super();
    

    @PrePersist
    @Override
    protected void preInsert() 
        super.preInsert();
    

SCPerson 类:

@MappedSuperclass
@Getter
@Setter
@SuperBuilder
public class SCPerson 
[...]

【问题讨论】:

你怎么能确定这个查询只需要时间或其他操作?如果你只想要顶部元素,你只能获取它。 @Eklavya 我在我的配置中启用了“logging.level.org.hibernate.SQL=DEBUG”,我在日志中看到了它。否则,当我将此行更改为“List personList = new ArrayList()”时,滞后消失了。 在您的存储库中删除 @Transactional。我认为这与其他一些问题有关 该表有多少行?您可以使用 pageable 仅获取前一个元素,而不是从表 Ex 中获取所有数据。将所有数据映射到实体中需要时间。 @Eklavya 表人为空。 【参考方案1】:

最后我找到了解决方案,问题出在代码的另一部分。 在调用 getLinkedPerson() 方法之前,我有这行代码:

List<CNPerson> cnPersonList = cnPersonDao.findCnPersonNotLinkedWithPerson(obiekt.getLoid());

cnPersonList 在这里包含大约 70 000 个对象。

我改成:

List<Integer> ids = cnPersonDao.findCnPersonIdsNotLinkedWithPerson(obiekt.getLoid());

问题描述在这里:https://***.com/a/46258045/9678458

在休眠上下文刷新期间减速。万一你更新 太多的对象 ORM 引擎(比如说 Hibernate)必须下沉它们并且 将它们留在记忆中。从字面上看,Hibernate 必须具有所有旧状态和 更新对象的所有新状态。有时它会这样做 不理想。

您可以使用调试来表明这一点。尝试找到最慢的地方,然后 检查那里到底调用了什么。我可能猜它会变慢 当休眠更新缓存状态时关闭。

我认为这是因为实体 CNPerson 和 PersonLinked 是链接的,但我不确定:

@ManyToOne(fetch = FetchType.LAZY,
        cascade = CascadeType.MERGE, CascadeType.PERSIST)
@JoinTable(name = "cnperson_links",
        joinColumns = @JoinColumn(name = "cnperson_loid"),
        inverseJoinColumns = @JoinColumn(name = "person_id"))
private PersonLinked personLinked;

【讨论】:

以上是关于与 SQL 相比,Hibernate JPQL 查询非常慢的主要内容,如果未能解决你的问题,请参考以下文章

JPA 2.1 @ConstructorResult 和 @SqlResultSetMapping 与 JPQL SELECT NEW SomeConstructor(...) 相比有啥优势?

Spring JPA 和 Hibernate 的 JPQL 查询错误

SQL,HQL,CQL,JPQL了解

JPA JPQL 怎么判断某个字段是不是为空

Hibernate:如何通过连接字符串来创建 JPQL?

如何使用 Hibernate 5 正确创建 JPQL 查询(不推荐使用查询类型)