V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
XiaoJiang9527
V2EX  ›  程序员

Java Hibernate 复杂实体关联下,简单查询都很慢,如何优化?

  •  1
     
  •   XiaoJiang9527 · 298 天前 · 5489 次点击
    这是一个创建于 298 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近正在整改一个老项目,里面的表结构错综复杂,实体中的映射表字段基本都增加了 @ManyToOne 和 @JoinColumn 注解,导致很多简单单表查询都需要执行很久。 但是打印具体 hibernate SQL 日志,Explain 下,也不存在什么性能问题,各位大佬们,怎么解?

    65 条回复    2024-08-16 20:35:24 +08:00
    issakchill
        1
    issakchill  
       298 天前
    是不是查单表的时候 也自动遍历关联查了关联表的数据?
    nothingistrue
        2
    nothingistrue  
       298 天前
    没 DDD 小聚合,或者类似的数据模型,不要用 Hibernate 。当然就算是用了 DDD ,也是用通过读写分离,来避开复杂查询的。
    BiChengfei
        3
    BiChengfei  
       298 天前
    我不用 @ManyToOne 和 @JoinColumn ,都是在代码中手动关联查询,感觉这两个注解隐藏了太多细节。sql 都有了,就一个流程一个流程看呗,要么数据多,要么流程多
    XiaoJiang9527
        4
    XiaoJiang9527  
    OP
       298 天前
    @issakchill 没错,一个简单查询能打印 6 条查询语句,有一个 SQL 甚至 JOIN 了 9 张表
    XiaoJiang9527
        5
    XiaoJiang9527  
    OP
       298 天前
    @nothingistrue 看来复杂查询只能全部拆分成原生 SQL 去解决了。
    XiaoJiang9527
        6
    XiaoJiang9527  
    OP
       298 天前
    @BiChengfei 流程一个一个看,给我震惊的不行,一个 hibernate 的 Query 方法,能打印 6 条查询语句 !!!
    nothingistrue
        7
    nothingistrue  
       298 天前
    1 ,数据库弄个视图,然后单独给这个试图搞个纯查询的实体吧。2 ,如果 1 不可行,跑路。

    这么多关联,你这是个屁的简单查询。
    nothingistrue
        8
    nothingistrue  
       298 天前
    数据模型太烂,上谁都管不了。换成 SQL 也是复杂 SQL ,可能更慢。
    yor1g
        9
    yor1g  
       298 天前
    @ManyToOne 都开延迟 按需返回字段
    shyangs
        10
    shyangs  
       298 天前
    JOIN 了 9 張表, 不能算簡單查詢吧.

    你可以試試手寫 SQL, 9 表 JOIN , 看能提升多少.
    lybcyd
        11
    lybcyd  
       298 天前
    是不是 N+1 ,看看语句具体是怎么关联的
    Sigrdirfa
        12
    Sigrdirfa  
       298 天前
    简单查询在我的概念里最狭义的是单表与其所对应的 Java 对象进行 crud 操作,稍微宽泛一点的是和一到两张有关联关系的表(关联表无其他与其关联表需要被查询)进行连表查询。

    你这个对象是不是过于复杂了?
    XiaoJiang9527
        13
    XiaoJiang9527  
    OP
       298 天前
    @nothingistrue 在 Hibernate 里面,我把 "service.queryData(conditionBean);" 称之为一个单表查询。
    XiaoJiang9527
        14
    XiaoJiang9527  
    OP
       298 天前
    @nothingistrue 我也非常担心更改原生 SQL 后,更为糟糕。
    XiaoJiang9527
        15
    XiaoJiang9527  
    OP
       298 天前
    @yor1g 实体之间的关系绑定,hibernate 好像没办法按需返回所需字段吧?
    XiaoJiang9527
        16
    XiaoJiang9527  
    OP
       298 天前
    @lybcyd 打印出的 SQL ,用来分析,确实点问题没有,都是类似主外键查询,也是正常走的索引,就是极慢。一个接口能打印 30 个 SQL 。
    XiaoJiang9527
        17
    XiaoJiang9527  
    OP
       298 天前
    @Sigrdirfa 数据模型太烂了,写的也烂。
    XiaoJiang9527
        18
    XiaoJiang9527  
    OP
       298 天前
    @shyangs 还别说,手写 9 个表 join 查询 0.2 s ,跟 hibernate 输出的 SQL 基本一致。
    Sanshi4396
        19
    Sanshi4396  
       298 天前
    直接在 Repo 里用 @Query 写原生 sql 语句,查询你想要的东西呗,不要用它的方法查询。
    如果原生 sql 都很慢,那不能怪 hibernate 了。
    Sanshi4396
        20
    Sanshi4396  
       297 天前
    还有你的查询是不是返回了 Page<>分页对象。这个对象返回的时候,是会多次查询的。
    1 、查询你想要的单页数据 2 、查询数据总数
    XiaoJiang9527
        21
    XiaoJiang9527  
    OP
       297 天前
    @Sanshi4396 嗯,值得试一下
    ZSeptember
        22
    ZSeptember  
       297 天前
    SQL 优化空间不大吧,主要是使用姿势。
    什么复杂业务 ORM 用的这么花,互联网不是不让 join 吗
    yor1g
        23
    yor1g  
       297 天前
    @XiaoJiang9527 可以按需的 开了延迟加载可以基本解决 n+1 问题 再优化的话要按需返回字段 + 基础表开 2 级缓存
    XiaoJiang9527
        24
    XiaoJiang9527  
    OP
       297 天前
    @ZSeptember 互联网不让用,奈何 hibernate 机制在哪里,数据少和初期开发的时候压根不会考虑,后期优化头都大
    issakchill
        25
    issakchill  
       297 天前
    @XiaoJiang9527 #4 简单试试加个 @Lazy
    XiaoJiang9527
        26
    XiaoJiang9527  
    OP
       297 天前
    @ZSeptember 只能提取业务结果,将 SQL 尽可能压缩,取按需内容。
    XiaoJiang9527
        27
    XiaoJiang9527  
    OP
       297 天前
    @issakchill 最开始就是尝试过,改了之后一大堆空指针异常,改了 5 个后,放弃了这个方案。
    XiaoJiang9527
        28
    XiaoJiang9527  
    OP
       297 天前
    @yor1g 我来试试 i
    Goooooos
        29
    Goooooos  
       297 天前   ❤️ 4
    我技术水平不够,还是喜欢用 mybatis ,这样自己好把控细节

    建议 @ 一下 v2 上常常出来 diss mybatis 的 hibernate 大神来帮你优化
    xuanbg
        30
    xuanbg  
       297 天前
    性能问题唯有手写 sql 可解。
    UBcai
        31
    UBcai  
       297 天前
    勾起了我吐槽欲望。项目最开始架构要 Hibernate ,我说不行。我只要 a 表的 1,2,3 字段,hibernate 会查询全部字段,并且会查询 a 表的关联表 b 的字段,逻辑复杂后肯定有性能问题。架构说小公司,性能要不了那么高要求。说白了就是 hibernate 不用写 sql,当时他还没决定入职,怎么简单怎么来。老板还让我多和架构学学,不要还没学会走就想跑。后面果然不出我所料,出了各种大问题。你们敢想象 30 个人同时用,服务器直接崩了。
    zhazi
        32
    zhazi  
       297 天前
    /*
    * Copyright (c) 2008, 2019 Oracle and/or its affiliates. All rights reserved.
    *
    * This program and the accompanying materials are made available under the
    * terms of the Eclipse Public License v. 2.0 which is available at
    * http://www.eclipse.org/legal/epl-2.0,
    * or the Eclipse Distribution License v. 1.0 which is available at
    * http://www.eclipse.org/org/documents/edl-v10.php.
    *
    * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
    */

    // Contributors:
    // Linda DeMichiel - 2.1
    // Linda DeMichiel - 2.0


    package javax.persistence;

    /**
    * Defines strategies for fetching data from the database.
    * The <code>EAGER</code> strategy is a requirement on the persistence
    * provider runtime that data must be eagerly fetched. The
    * <code>LAZY</code> strategy is a hint to the persistence provider
    * runtime that data should be fetched lazily when it is
    * first accessed. The implementation is permitted to eagerly
    * fetch data for which the <code>LAZY</code> strategy hint has been
    * specified.
    *
    * <pre>
    * Example:
    * &#064;Basic(fetch=LAZY)
    * protected String getName() { return name; }
    * </pre>
    *
    * @see Basic
    * @see ElementCollection
    * @see ManyToMany
    * @see OneToMany
    * @see ManyToOne
    * @see OneToOne
    * @since 1.0
    */
    public enum FetchType {

    /** Defines that data can be lazily fetched. */
    LAZY,

    /** Defines that data must be eagerly fetched. */
    EAGER
    }

    不看文档,不查手册的吐槽我不是很认可
    XiaoJiang9527
        33
    XiaoJiang9527  
    OP
       297 天前
    @Goooooos 哈哈,又臭有长的代码,没有那个大佬看得下去叭
    aragakiyuii
        34
    aragakiyuii  
       297 天前 via Android
    @UBcai projection query
    XiaoJiang9527
        35
    XiaoJiang9527  
    OP
       297 天前
    @UBcai 目前我这个更是离谱,不过都是同一种类型的问题,先归结为机制问题吧
    XiaoJiang9527
        36
    XiaoJiang9527  
    OP
       297 天前   ❤️ 2
    @zhazi 这个机制调整会导致我后续业务代码空指针,而且很多。 /狗头
    zhazi
        37
    zhazi  
       297 天前
    @XiaoJiang9527 晒代码,别虚空打靶
    Nosub
        38
    Nosub  
       297 天前 via Android
    了解一下,EntityGraph
    Nosub
        39
    Nosub  
       297 天前
    @UBcai 投影 Projections 和 DTO 了解一下,如果遇到问题只会吐槽,肯定还是无法掌握 Hibernate 的。
    Nosub
        40
    Nosub  
       297 天前
    还有个问题,很多人没有搞清楚 JPA ,Hibernate 和 Spring Data JPA 的关系,建议花点时间搞清楚这些概念,OP 说的是 Spring Data JPA 速度慢。
    Nosub
        41
    Nosub  
       297 天前
    nothingistrue
        42
    nothingistrue  
       297 天前
    @Nosub #40 JPA 是标准,Hibernate 是一种实现 ,Spirng Data JPA 跟 Hibernate 就是 Spring Boot 跟 Spring 的关系。你这来源是瞎比较,你不能下。
    dog82
        43
    dog82  
       297 天前
    实在不行就上物化视图,用空间换时间
    Nosub
        44
    Nosub  
       297 天前
    @nothingistrue Hibernate 的开发团队写的,瞎在哪儿。
    nothingistrue
        45
    nothingistrue  
       297 天前
    @Nosub #41 上原文链接
    XiaoJiang9527
        46
    XiaoJiang9527  
    OP
       297 天前
    @dog82 准备重新提炼业务,切换实现方案,因为目前数据量不是很大,先用 SQL 开路解决
    zhazi
        47
    zhazi  
       297 天前
    水平差,冤框架。让上代码也不上,block
    Nosub
        48
    Nosub  
       297 天前
    @nothingistrue 你可以看看《 Java Persistence with Hibernate Second Edition 》,数据来自这本书,作者你也可以看看。
    XiaoJiang9527
        49
    XiaoJiang9527  
    OP
       297 天前
    @Nosub 好的,感谢
    forbreak
        50
    forbreak  
       297 天前
    说 hibernate 慢的,都是不愿意学高级用法。埋头狂写,能不慢吗。
    wangYQ
        51
    wangYQ  
       297 天前
    @XiaoJiang9527 但是这样的成本有点大,
    1.老项目,其中业务饱经沧桑,如果有很熟悉业务的完全可以重新改,如果不存在业务完全了解的人,还是不要贸然干,不知道有什么特殊业务的坑藏在细小的地方。
    2.SQL 单独执行很快,只是关联查询慢,看能不能把需要的数据通过数据库层面视图,冗余字段,减少一个接口调用太多的 SQL ,减少开销。
    3.如果项目后期有更换数据库的需求,Hibernate ,JPA 这种还算是比较方便,换个方言能解决一大部分的事情,要是都是原生 SQL 用了一些特殊函数,改数据库的话工作量特别大
    XiaoJiang9527
        52
    XiaoJiang9527  
    OP
       297 天前
    @wangYQ 是的,目前真是没办法下手。
    目前我对项目的选型和方案制定倒是没有很大的敌意,仅想寻找一种合适的解决方案。
    上面大佬的那些方案目前正在一个个尝试。
    nothingistrue
        53
    nothingistrue  
       297 天前
    @Nosub #45 这本书是付费书籍,你这又连完整原文截图都没有,你这是在自认「断章取义」,还是在自认「没看原文而是看了某人的断章取义」。

    这个图应该是真的,但是它需要结合完整原文才能知道说得是什么。这里比较的很可能是,「直接用 Hibernate 的 API 」、「用 JDK 的 JPA 」 、用「 Spring Data JPA 」 ,这三种,使用 Hibernate 的途径的性能。 三个都是 Hibernate ,只不过使用方式不同。
    iyiluo
        54
    iyiluo  
       297 天前
    屎山项目,除非整个模块重构,任何试图在旧代码上优化的行为都是往屎山上拉屎
    wangYQ
        55
    wangYQ  
       297 天前
    @XiaoJiang9527 不能抱有敌意,根据对技术喜好看问题。如果你换成 mybatis 也会面临其他的问题,也没有办法保证百分百没有其他的问题。在有限的空间内做最大程度的优化就好,即使就是搞不定了,需要重构,也是需要多人讨论,评审后,再下结论,做选型,做改造。说句不好听的话,接手了屎山,改造就好比屎山雕花,有时候盲目的重构,弄完发现不过是在屎山旁边又拉了一坨。
    XiaoJiang9527
        56
    XiaoJiang9527  
    OP
       297 天前
    @iyiluo 嗯,确实烂的一塌糊涂
    XiaoJiang9527
        57
    XiaoJiang9527  
    OP
       297 天前
    @wangYQ 没错,这种工作吃力不讨好的,没有一种方案是能够保证百分百解决的。
    yidinghe
        58
    yidinghe  
       297 天前
    Hibernate 的初衷就是想偷懒,想回避关系数据库,结果到了最后,发现在 Hibernate 体系里面学那么多、写那么多,依旧耗费大量精力,而且还不如一条手写 SQL 搞定。

    Nosub
        59
    Nosub  
       297 天前
    给 OP 提供一个解决方案,比如有个文章表,和一个 User 表,我要查询所有文章列表,文章列表又要返回这篇文章的作者信息,我这里写了一个投影和一个 DTO ,用 nativeQuery 方式查询。

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserInfoDto {
    private Long id;
    private String name;
    private String avatar;
    }

    public interface PostInfo {
    long getCreateTime();
    long getModifiedTime();
    Long getId();
    String getTitle();
    String getSummary();
    String getContent();
    @Value("#{new com.momo.xxx.dto.UserInfoDto(target.user_id, target.user_name, target.user_avatar)}")
    UserInfoDto getAuthor();
    }


    user_info 表示用户表,Post 表示文章列表。

    @Query(value = "SELECT p.id,user_info.id AS user_id, user_info.name AS user_name, user_info.avatar AS user_avatar,p.title,p.summary,p.CONTENT,p.state,p.create_time AS createTime,p.modified_time AS modifiedTime FROM Post p JOIN user_info ON user_info.id=p.author_id WHERE p.is_delete = FALSE AND p.STATE = 5 ORDER BY p.ID DESC",
    nativeQuery = true)
    Page<PostInfo> findAllPublishedPostsNative(Pageable pageable);

    如果直接用 Spring Data Jpa 查询 10 条数据大概要 4192ms ,可能更慢,用 Native 方式可能只要 130ms ,这个数据只是我的一个测试数据;
    TGhoull
        60
    TGhoull  
       296 天前
    @UBcai 你应该这么想想,Hibernate 要是如你所说的那么拉,它还能在国外称霸那么多年吗?
    nothingistrue
        61
    nothingistrue  
       296 天前
    @yidinghe #55 你可以再深入一点,什么 Mybatis ,什么 Service ,什么 Controller 都是多余的,再继续一点什么前端框架也是多余的—— 原生 javascript + SQL 就够了。
    nothingistrue
        62
    nothingistrue  
       295 天前
    @Nosub #56 作者信息,是「文章」的属性,它来自于「用户」,但与「用户」不存在实体关联关系。换个更直白的说法,文章的作者姓名,跟用户的名称,通过用户 ID 能映射,但他们不是同一个值。如果再仔细看,用户名称可变更,但作者姓名是不可变更的,它们连等值映射都做不了。你的实体关联关系是错误的。

    请注意,上面说的是实体关联关系,即 Entity-Relation ,这是数据库设计的概要模型阶段,与 ORM 、DDD 都没关系。

    当把 ER 弄明白之后,在看「查询文章列表,其中要带上作者信息」这个查询,那就根本不存在任何性能问题,因为这是单表查询。当然这个不是没有其他付出的,需要额外处理「文章」的 userId 、authorName 、authorAvatar ,跟 「用户」的 id 、name 、avatar 之间的一致性问题。不过这个原本就是之前漏掉的业务功能,严格的说也不算是额外付出。处理起来也不是那么麻烦:
    Post.userId - User.id 这是不变量,只需要自然保存即可;
    Post.authorName - User.name ,实际业务上,这里已经是不相干的俩属性了,根本无需同步,如果功能上设计成需要同步的,见下面 avatar 的同步处理;
    Post.authorAvatar - User.avatar ,需要 User 修改 avatar 的时候,利用观察者模式、或者事件模式,让 Post (以及其他任何对此修改感兴趣的实体)连带去修改 authorAvatar (不过,如果 avatar 是实时图片的话,那么不如把 Avatar 也独立成单独实体,Post 、User 都只存不可变的 avatarId ,前端显示/数据导出的时候,再根据 avatarId 去实时获取最新图片);
    UBcai
        63
    UBcai  
       295 天前
    @TGhoull 看来是我没写清楚,我没说 hibernate 拉。hibernate 有学习成本,然后架构其实也不怎么会,他就是还没确定入职,想简单一点,导致了后面的一系列问题。然后还有个问题,很多甲方公司有 dba ,都是禁止外键,我们项目每次改的 sql 都用 flyway 记录版本,到甲方服务器根本更新不了。你敢信,每次更新我全部都是手动运行 sql ,手动改 flyway 版本状态。人都麻了
    INCerry
        64
    INCerry  
       295 天前
    是什么数据库? mysql 之类的多表查性能本来就很差
    stone981023655
        65
    stone981023655  
       101 天前
    @UBcai 就是熟练度不高
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5933 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 37ms · UTC 02:18 · PVG 10:18 · LAX 18:18 · JFK 21:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.