最近正在整改一个老项目,里面的表结构错综复杂,实体中的映射表字段基本都增加了 @ManyToOne 和 @JoinColumn 注解,导致很多简单单表查询都需要执行很久。 但是打印具体 hibernate SQL 日志,Explain 下,也不存在什么性能问题,各位大佬们,怎么解?
1
issakchill 304 天前
是不是查单表的时候 也自动遍历关联查了关联表的数据?
|
2
nothingistrue 304 天前
没 DDD 小聚合,或者类似的数据模型,不要用 Hibernate 。当然就算是用了 DDD ,也是用通过读写分离,来避开复杂查询的。
|
3
BiChengfei 304 天前
我不用 @ManyToOne 和 @JoinColumn ,都是在代码中手动关联查询,感觉这两个注解隐藏了太多细节。sql 都有了,就一个流程一个流程看呗,要么数据多,要么流程多
|
4
XiaoJiang9527 OP @issakchill 没错,一个简单查询能打印 6 条查询语句,有一个 SQL 甚至 JOIN 了 9 张表
|
5
XiaoJiang9527 OP @nothingistrue 看来复杂查询只能全部拆分成原生 SQL 去解决了。
|
6
XiaoJiang9527 OP @BiChengfei 流程一个一个看,给我震惊的不行,一个 hibernate 的 Query 方法,能打印 6 条查询语句 !!!
|
7
nothingistrue 304 天前
1 ,数据库弄个视图,然后单独给这个试图搞个纯查询的实体吧。2 ,如果 1 不可行,跑路。
这么多关联,你这是个屁的简单查询。 |
8
nothingistrue 304 天前
数据模型太烂,上谁都管不了。换成 SQL 也是复杂 SQL ,可能更慢。
|
10
shyangs 304 天前
JOIN 了 9 張表, 不能算簡單查詢吧.
你可以試試手寫 SQL, 9 表 JOIN , 看能提升多少. |
11
lybcyd 304 天前
是不是 N+1 ,看看语句具体是怎么关联的
|
12
Sigrdirfa 304 天前
简单查询在我的概念里最狭义的是单表与其所对应的 Java 对象进行 crud 操作,稍微宽泛一点的是和一到两张有关联关系的表(关联表无其他与其关联表需要被查询)进行连表查询。
你这个对象是不是过于复杂了? |
13
XiaoJiang9527 OP @nothingistrue 在 Hibernate 里面,我把 "service.queryData(conditionBean);" 称之为一个单表查询。
|
14
XiaoJiang9527 OP @nothingistrue 我也非常担心更改原生 SQL 后,更为糟糕。
|
15
XiaoJiang9527 OP @yor1g 实体之间的关系绑定,hibernate 好像没办法按需返回所需字段吧?
|
16
XiaoJiang9527 OP @lybcyd 打印出的 SQL ,用来分析,确实点问题没有,都是类似主外键查询,也是正常走的索引,就是极慢。一个接口能打印 30 个 SQL 。
|
17
XiaoJiang9527 OP @Sigrdirfa 数据模型太烂了,写的也烂。
|
18
XiaoJiang9527 OP @shyangs 还别说,手写 9 个表 join 查询 0.2 s ,跟 hibernate 输出的 SQL 基本一致。
|
19
Sanshi4396 304 天前
直接在 Repo 里用 @Query 写原生 sql 语句,查询你想要的东西呗,不要用它的方法查询。
如果原生 sql 都很慢,那不能怪 hibernate 了。 |
20
Sanshi4396 304 天前
还有你的查询是不是返回了 Page<>分页对象。这个对象返回的时候,是会多次查询的。
1 、查询你想要的单页数据 2 、查询数据总数 |
21
XiaoJiang9527 OP @Sanshi4396 嗯,值得试一下
|
22
ZSeptember 304 天前
SQL 优化空间不大吧,主要是使用姿势。
什么复杂业务 ORM 用的这么花,互联网不是不让 join 吗 |
23
yor1g 304 天前
@XiaoJiang9527 可以按需的 开了延迟加载可以基本解决 n+1 问题 再优化的话要按需返回字段 + 基础表开 2 级缓存
|
24
XiaoJiang9527 OP @ZSeptember 互联网不让用,奈何 hibernate 机制在哪里,数据少和初期开发的时候压根不会考虑,后期优化头都大
|
25
issakchill 304 天前
@XiaoJiang9527 #4 简单试试加个 @Lazy
|
26
XiaoJiang9527 OP @ZSeptember 只能提取业务结果,将 SQL 尽可能压缩,取按需内容。
|
27
XiaoJiang9527 OP @issakchill 最开始就是尝试过,改了之后一大堆空指针异常,改了 5 个后,放弃了这个方案。
|
28
XiaoJiang9527 OP @yor1g 我来试试 i
|
29
Goooooos 304 天前 4
我技术水平不够,还是喜欢用 mybatis ,这样自己好把控细节
建议 @ 一下 v2 上常常出来 diss mybatis 的 hibernate 大神来帮你优化 |
30
xuanbg 304 天前
性能问题唯有手写 sql 可解。
|
31
UBcai 304 天前
勾起了我吐槽欲望。项目最开始架构要 Hibernate ,我说不行。我只要 a 表的 1,2,3 字段,hibernate 会查询全部字段,并且会查询 a 表的关联表 b 的字段,逻辑复杂后肯定有性能问题。架构说小公司,性能要不了那么高要求。说白了就是 hibernate 不用写 sql,当时他还没决定入职,怎么简单怎么来。老板还让我多和架构学学,不要还没学会走就想跑。后面果然不出我所料,出了各种大问题。你们敢想象 30 个人同时用,服务器直接崩了。
|
32
zhazi 304 天前
/*
* 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: * @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 } 不看文档,不查手册的吐槽我不是很认可 |
33
XiaoJiang9527 OP @Goooooos 哈哈,又臭有长的代码,没有那个大佬看得下去叭
|
34
aragakiyuii 304 天前 via Android
@UBcai projection query
|
35
XiaoJiang9527 OP @UBcai 目前我这个更是离谱,不过都是同一种类型的问题,先归结为机制问题吧
|
36
XiaoJiang9527 OP @zhazi 这个机制调整会导致我后续业务代码空指针,而且很多。 /狗头
|
37
zhazi 304 天前
@XiaoJiang9527 晒代码,别虚空打靶
|
38
Nosub 304 天前 via Android
了解一下,EntityGraph
|
40
Nosub 304 天前
还有个问题,很多人没有搞清楚 JPA ,Hibernate 和 Spring Data JPA 的关系,建议花点时间搞清楚这些概念,OP 说的是 Spring Data JPA 速度慢。
|
41
Nosub 304 天前
|
42
nothingistrue 304 天前
@Nosub #40 JPA 是标准,Hibernate 是一种实现 ,Spirng Data JPA 跟 Hibernate 就是 Spring Boot 跟 Spring 的关系。你这来源是瞎比较,你不能下。
|
43
dog82 304 天前
实在不行就上物化视图,用空间换时间
|
44
Nosub 304 天前
@nothingistrue Hibernate 的开发团队写的,瞎在哪儿。
|
45
nothingistrue 304 天前
@Nosub #41 上原文链接
|
46
XiaoJiang9527 OP @dog82 准备重新提炼业务,切换实现方案,因为目前数据量不是很大,先用 SQL 开路解决
|
47
zhazi 304 天前
水平差,冤框架。让上代码也不上,block
|
48
Nosub 304 天前
@nothingistrue 你可以看看《 Java Persistence with Hibernate Second Edition 》,数据来自这本书,作者你也可以看看。
|
49
XiaoJiang9527 OP @Nosub 好的,感谢
|
50
forbreak 304 天前
说 hibernate 慢的,都是不愿意学高级用法。埋头狂写,能不慢吗。
|
51
wangYQ 304 天前
@XiaoJiang9527 但是这样的成本有点大,
1.老项目,其中业务饱经沧桑,如果有很熟悉业务的完全可以重新改,如果不存在业务完全了解的人,还是不要贸然干,不知道有什么特殊业务的坑藏在细小的地方。 2.SQL 单独执行很快,只是关联查询慢,看能不能把需要的数据通过数据库层面视图,冗余字段,减少一个接口调用太多的 SQL ,减少开销。 3.如果项目后期有更换数据库的需求,Hibernate ,JPA 这种还算是比较方便,换个方言能解决一大部分的事情,要是都是原生 SQL 用了一些特殊函数,改数据库的话工作量特别大 |
52
XiaoJiang9527 OP |
53
nothingistrue 304 天前
@Nosub #45 这本书是付费书籍,你这又连完整原文截图都没有,你这是在自认「断章取义」,还是在自认「没看原文而是看了某人的断章取义」。
这个图应该是真的,但是它需要结合完整原文才能知道说得是什么。这里比较的很可能是,「直接用 Hibernate 的 API 」、「用 JDK 的 JPA 」 、用「 Spring Data JPA 」 ,这三种,使用 Hibernate 的途径的性能。 三个都是 Hibernate ,只不过使用方式不同。 |
54
iyiluo 304 天前
屎山项目,除非整个模块重构,任何试图在旧代码上优化的行为都是往屎山上拉屎
|
55
wangYQ 304 天前
@XiaoJiang9527 不能抱有敌意,根据对技术喜好看问题。如果你换成 mybatis 也会面临其他的问题,也没有办法保证百分百没有其他的问题。在有限的空间内做最大程度的优化就好,即使就是搞不定了,需要重构,也是需要多人讨论,评审后,再下结论,做选型,做改造。说句不好听的话,接手了屎山,改造就好比屎山雕花,有时候盲目的重构,弄完发现不过是在屎山旁边又拉了一坨。
|
56
XiaoJiang9527 OP @iyiluo 嗯,确实烂的一塌糊涂
|
57
XiaoJiang9527 OP @wangYQ 没错,这种工作吃力不讨好的,没有一种方案是能够保证百分百解决的。
|
58
yidinghe 304 天前
|
59
Nosub 304 天前
给 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 ,这个数据只是我的一个测试数据; |
61
nothingistrue 302 天前
@yidinghe #55 你可以再深入一点,什么 Mybatis ,什么 Service ,什么 Controller 都是多余的,再继续一点什么前端框架也是多余的—— 原生 javascript + SQL 就够了。
|
62
nothingistrue 302 天前
@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 去实时获取最新图片); |
63
UBcai 302 天前
@TGhoull 看来是我没写清楚,我没说 hibernate 拉。hibernate 有学习成本,然后架构其实也不怎么会,他就是还没确定入职,想简单一点,导致了后面的一系列问题。然后还有个问题,很多甲方公司有 dba ,都是禁止外键,我们项目每次改的 sql 都用 flyway 记录版本,到甲方服务器根本更新不了。你敢信,每次更新我全部都是手动运行 sql ,手动改 flyway 版本状态。人都麻了
|
64
INCerry 302 天前
是什么数据库? mysql 之类的多表查性能本来就很差
|
65
stone981023655 108 天前
@UBcai 就是熟练度不高
|