You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA与Hibernate查询性能异常求助:SQL在Workbench秒级执行

性能问题排查:Hibernate查询耗时过长但原生SQL执行快速

问题背景

在应用性能测试中,遇到Hibernate执行查询获取约18000条记录耗时约90秒,但相同的SQL语句在MySQL Workbench中执行仅需1秒。相关代码信息如下:

实体类

@Entity @Table(name = "merged_bill_items_data")
public class MergedBillItemData{

    @Id
    @AccessType("property")
    @Column(unique = true, nullable = false)
    private String id;

    @Temporal(TemporalType.DATE)
    @Column(name = "start_date", nullable = false)
    @Type(type = "com.iblogix.analytic.type.LocalDateDBType")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") 
    private LocalDate startDate;

    @Temporal(TemporalType.DATE)
    @Column(name = "end_date", nullable = false)
    @Type(type = "com.iblogix.analytic.type.LocalDateDBType")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") 
    private LocalDate endDate;

    @Temporal(TemporalType.DATE)
    @Column(name = "statement_date", nullable = false)
    @Type(type = "com.iblogix.analytic.type.LocalDateDBType")
    private LocalDate statementDate;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "analysis_id", nullable = false)
    private Analysis analysis;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "bill_item_def_id", nullable = false)
    private BillItemDefinition billItemDefinition;
    // 省略其他字段
}

Repository类

public interface MergedBillItemsDataRepository
extends
GenericJpaRepository<MergedBillItemData, String>,
JpaSpecificationExecutor {
}

原始命名查询

@NamedQuery(name = "MergedBillItemData.findByUserAndEnergyTypeAndDisplayMonthRangeByAdjType", 
            query = "Select mbid From BuildingUsers bu, MergedBillItemData mbid " +
                    "where bu.user.id=:userId and bu.building.id=mbid.analysis.building.id " +
                    "and mbid.energyType.id =:energyTypeId and mbid.adjustmentType =:adjustmentType " +
                    "and mbid.displayMonth >= :startDate and mbid.displayMonth <= :endDate " +
                    "order by mbid.displayMonth asc"),

最初怀疑是两个EAGER加载的关联实体导致N+1查询,因此尝试了以下优化方案,但均无效果:

尝试过的优化方案

方案1:改用DTO投影避免EAGER加载

自定义DTOMergedBillItemDataWrapper,通过构造函数投影仅查询所需字段,避免加载关联实体,但性能无提升:

@NamedQuery(name = "MergedBillItemData.getBillItemsByUserIdAndEnergyTypeAndDisplayMonth", 
            query = "select new com.iblogix.analytic.dto.MergedBillItemDataWrapper(" +
                    "mbid.id, mbid.startDate,mbid.endDate, mbid.statementDate, " +
                    "mbid.analysis.id as analysisId,mbid.analysis.building.id as buildingId, " +
                    "mbid.billItemDefinition.id as billItemDefinitionId, " +
                    "mbid.billItemDefinition.ffBillItemName,mbid.billItemDefinition.utilityBillItemName, " +
                    "mbid.billItemDefinition.ffBillItemCategory,mbid.energyType.id as energyTypeId, " +
                    "mbid.meterReadDatesAligned, mbid.cost,mbid.statementDatesAligned," +
                    "mbid.numberOfStatements,mbid.thirdPartyBilled,mbid.itemUsageValue," +
                    "mbid.unitId,mbid.unitPrice,mbid.readingType,mbid.displayMonth, mbid.adjustmentType) " +
                    "From MergedBillItemData mbid , BuildingUsers bu  " +
                    "where bu.user.id=:userId and bu.building.id=mbid.analysis.building.id " +
                    "and mbid.energyType.id =:energyTypeId and mbid.adjustmentType =:adjustmentType " +
                    "and mbid.displayMonth >= :startDate and mbid.displayMonth <= :endDate " +
                    "order by mbid.displayMonth asc"),

方案2:改用原生查询+结果集映射

编写原生SQL并通过@SqlResultSetMapping映射结果,性能仍无提升:

@SqlResultSetMappings({
    @SqlResultSetMapping(name = "MBIDMapping", 
                         // 省略具体映射配置
                        )
})
@NamedNativeQueries({
@NamedNativeQuery(name = "MergedBillItemData.getBillItemsByUserIdAndEnergyTypeAndDisplayMonthAndAdjustmentType", 
                 query = "select mbid.id, mbid.start_date as startDate, mbid.end_date as endDate, " +
                         "mbid.statement_date as statementDate, mbid.analysis_id as analysisId, " +
                         "b.id as buildingId, mbid.bill_item_def_id as billItemDefinitionId," +
                         "bd.ff_util_bill_item_name as ffBillItemName, bd.util_bill_item_name as utilityBillItemName," +
                         "bd.ff_util_bill_item_category as ffBillItemCategory,mbid.energy_type_id as energyTypeId, " +
                         "mbid.are_meter_read_dates_aligned as meterReadDatesAligned, mbid.cost as cost," +
                         "mbid.are_statement_dates_aligned as statementDatesAligned, mbid.number_of_statements as numberOfStatements, " +
                         "mbid.third_party_billed as thirdPartyBilled,mbid.item_usage_value as itemUsageValue, " +
                         "mbid.unit_id as unitId, mbid.unit_price as unitPrice, mbid.reading_type as readingType, " +
                         "mbid.display_month as displayMonth, mbid.adjustment_type as adjustmentType " +
                         "from building_users bu " +
                         "INNER JOIN user u ON bu.user_id=u.id " +
                         "INNER JOIN building b ON bu.building_id=b.id " +
                         "INNER JOIN analysis a ON a.building_id=b.id " +
                         "INNER JOIN merged_bill_items_data mbid ON mbid.analysis_id=a.analysis_id " +
                         "INNER JOIN energy_type et ON mbid.energy_type_id=et.id " +
                         "INNER JOIN bill_item_defs bd ON mbid.bill_item_def_id= bd.id " +
                         "where bu.user_id=:userId " +
                         "and mbid.energy_type_id =:energyTypeId " +
                         "and mbid.display_month >= :startDate " +
                         "and mbid.display_month <= :endDate " +
                         "and mbid.adjustment_type =:adjustmentType " +
                         "order by mbid.display_month asc ", 
                 resultSetMapping = "MBIDMapping"),
})

核心疑问

为何相同SQL在Workbench中执行极快,但通过Hibernate执行耗时长达90秒?

排查方向

  • 网络与连接池:检查应用服务器与数据库之间的网络延迟、带宽,对比Workbench所在机器与应用服务器的网络环境;同时确认数据库连接池配置(如最大连接数、等待超时)是否合理,是否存在连接等待。
  • Hibernate结果映射开销:开启Hibernate统计日志(hibernate.generate_statistics=true),查看结果集处理的耗时;检查自定义类型LocalDateDBType的转换逻辑是否存在性能瓶颈,18000条记录的对象映射可能累积大量开销。
  • 参数绑定与执行计划:开启Hibernate SQL日志,复制带参数的SQL到Workbench执行,确认是否仍快速;检查参数类型是否与数据库字段匹配(如日期参数是否被转成字符串导致索引失效),对比Hibernate执行时的数据库执行计划与Workbench的差异。
  • 事务与隐式加载:确认查询是否在大事务中执行,事务上下文可能带来额外开销;检查是否存在隐式关联加载(即使使用DTO投影,Hibernate是否仍触发了不必要的查询),查看SQL日志是否有额外的N+1查询。
  • JVM内存与GC:检查JVM堆内存配置是否足够,查看GC日志是否存在频繁GC或Full GC导致的停顿,18000条对象加载可能需要足够的内存空间,GC停顿会拉长整体耗时。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 21:15:35