Java Hibernate 复杂实体关联下,简单查询都很慢,如何优化? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
XiaoJiang9527
V2EX    程序员

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

  •  1
     
  •   XiaoJiang9527 2024-02-02 09:45:39 +08:00 7375 次点击
    这是一个创建于 618 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    这么多关联,你这是个屁的简单查询。
    nothingistrue
        8
    nothingistrue  
       2024-02-02 10:00:13 +08:00
    数据模型太烂,上谁都管不了。换成 SQL 也是复杂 SQL ,可能更慢。
    yor1g
        9
    yor1g  
       2024-02-02 10:02:01 +08:00
    @ManyToOne 都开延迟 按需返回字段
    shyangs
        10
    shyangs  
       2024-02-02 10:02:13 +08:00
    JOIN 了 9 表, 不能算查吧.

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

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

    建议 @ 一下 v2 上常常出来 diss mybatis 的 hibernate 大神来帮你优化
    xuanbg
        30
    xuanbg  
       2024-02-02 10:58:48 +08:00
    性能问题唯有手写 sql 可解。
    UBcai
        31
    UBcai  
       2024-02-02 11:16:42 +08:00
    勾起了我吐槽欲望。项目最开始架构要 Hibernate ,我说不行。我只要 a 表的 1,2,3 字段,hibernate 会查询全部字段,并且会查询 a 表的关联表 b 的字段,逻辑复杂后肯定有性能问题。架构说小公司,性能要不了那么高要求。说白了就是 hibernate 不用写 sql,当时他还没决定入职,怎么简单怎么来。老板还让我多和架构学学,不要还没学会走就想跑。后面果然不出我所料,出了各种大问题。你们敢想象 30 个人同时用,服务器直接崩了。
    zhazi
        32
    zhazi  
       2024-02-02 11:35:41 +08:00
    /*
    * 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
       2024-02-02 11:39:42 +08:00
    @Goooooos 哈哈,又臭有长的代码,没有那个大佬看得下去叭
    aragakiyuii
        34
    aragakiyuii  
       2024-02-02 11:40:32 +08:00 via Android
    @UBcai projection query
    XiaoJiang9527
        35
    XiaoJiang9527  
    OP
       2024-02-02 11:40:54 +08:00
    @UBcai 目前我这个更是离谱,不过都是同一种类型的问题,先归结为机制问题吧
    XiaoJiang9527
        36
    XiaoJiang9527  
    OP
       2024-02-02 11:44:21 +08:00   2
    @zhazi 这个机制调整会导致我后续业务代码空指针,而且很多。 /狗头
    zhazi
        37
    zhazi  
       2024-02-02 13:05:05 +08:00
    @XiaoJiang9527 晒代码,别虚空打靶
    Nosub
        38
    Nosub  
       2024-02-02 13:33:58 +08:00 via Android
    了解一下,EntityGraph
    Nosub
        39
    Nosub  
       2024-02-02 13:37:44 +08:00
    @UBcai 投影 Projections 和 DTO 了解一下,如果遇到问题只会吐槽,肯定还是无法掌握 Hibernate 的。
    Nosub
        40
    Nosub  
       2024-02-02 13:52:09 +08:00
    还有个问题,很多人没有搞清楚 JPA ,Hibernate 和 Spring Data JPA 的关系,建议花点时间搞清楚这些概念,OP 说的是 Spring Data JPA 速度慢。
    Nosub
        41
    Nosub  
       2024-02-02 13:57:36 +08:00
    nothingistrue
        42
    nothingistrue  
       2024-02-02 14:10:36 +08:00
    @Nosub #40 JPA 是标准,Hibernate 是一种实现 ,Spirng Data JPA 跟 Hibernate 就是 Spring Boot 跟 Spring 的关系。你这来源是瞎比较,你不能下。
    dog82
        43
    dog82  
       2024-02-02 14:14:43 +08:00
    实在不行就上物化视图,用空间换时间
    Nosub
        44
    Nosub  
       2024-02-02 14:16:10 +08:00
    @nothingistrue Hibernate 的开发团队写的,瞎在哪儿。
    nothingistrue
        45
    nothingistrue  
       2024-02-02 14:17:18 +08:00
    @Nosub #41 上原文链接
    XiaoJiang9527
        46
    XiaoJiang9527  
    OP
       2024-02-02 14:22:12 +08:00
    @dog82 准备重新提炼业务,切换实现方案,因为目前数据量不是很大,先用 SQL 开路解决
    zhazi
        47
    zhazi  
       2024-02-02 14:28:31 +08:00
    水平差,冤框架。让上代码也不上,block
    Nosub
        48
    Nosub  
       2024-02-02 14:44:44 +08:00
    @nothingistrue 你可以看看《 Java Persistence with Hibernate Second Edition 》,数据来自这本书,作者你也可以看看。
    XiaoJiang9527
        49
    XiaoJiang9527  
    OP
       2024-02-02 15:03:05 +08:00
    @Nosub 好的,感谢
    forbreak
        50
    forbreak  
       2024-02-02 15:18:48 +08:00
    说 hibernate 慢的,都是不愿意学高级用法。埋头狂写,能不慢吗。
    wangYQ
        51
    wangYQ  
       2024-02-02 15:22:53 +08:00
    @XiaoJiang9527 但是这样的成本有点大,
    1.老项目,其中业务饱经沧桑,如果有很熟悉业务的完全可以重新改,如果不存在业务完全了解的人,还是不要贸然干,不知道有什么特殊业务的坑藏在细小的地方。
    2.SQL 单独执行很快,只是关联查询慢,看能不能把需要的数据通过数据库层面视图,冗余字段,减少一个接口调用太多的 SQL ,减少开销。
    3.如果项目后期有更换数据库的需求,Hibernate ,JPA 这种还算是比较方便,换个方言能解决一大部分的事情,要是都是原生 SQL 用了一些特殊函数,改数据库的话工作量特别大
    XiaoJiang9527
        52
    XiaoJiang9527  
    OP
       2024-02-02 15:41:20 +08:00
    @wangYQ 是的,目前真是没办法下手。
    目前我对项目的选型和方案制定倒是没有很大的敌意,仅想寻找一种合适的解决方案。
    上面大佬的那些方案目前正在一个个尝试。
    nothingistrue
        53
    nothingistrue  
       2024-02-02 15:50:59 +08:00
    @Nosub #45 这本书是付费书籍,你这又连完整原文截图都没有,你这是在自认「断章取义」,还是在自认「没看原文而是看了某人的断章取义」。

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

    Nosub
        59
    Nosub  
       2024-02-02 17:51:44 +08:00
    给 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  
       2024-02-03 10:40:09 +08:00
    @UBcai 你应该这么想想,Hibernate 要是如你所说的那么拉,它还能在国外称霸那么多年吗?
    nothingistrue
        61
    nothingistrue  
       2024-02-04 09:58:37 +08:00
    @yidinghe #55 你可以再深入一点,什么 Mybatis ,什么 Service ,什么 Controller 都是多余的,再继续一点什么前端框架也是多余的 原生 Javascript + SQL 就够了。
    nothingistrue data-uid=
        62
    nothingistrue  
       2024-02-04 10:36:43 +08:00
    @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  
       2024-02-04 10:39:54 +08:00
    @TGhoull 看来是我没写清楚,我没说 hibernate 拉。hibernate 有学习成本,然后架构其实也不怎么会,他就是还没确定入职,想简单一点,导致了后面的一系列问题。然后还有个问题,很多甲方公司有 dba ,都是禁止外键,我们项目每次改的 sql 都用 flyway 记录版本,到甲方服务器根本更新不了。你敢信,每次更新我全部都是手动运行 sql ,手动改 flyway 版本状态。人都麻了
    INCerry
        64
    INCerry  
       2024-02-04 11:04:49 +08:00
    是什么数据库? mysql 之类的多表查性能本来就很差
    stone981023655
        65
    stone981023655  
       2024-08-16 20:35:24 +08:00
    @UBcai 就是熟练度不高
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2810 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 13:24 PVG 21:24 LAX 06:24 JFK 09:24
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86