当前位置: 首页 > news >正文

Spring Boot 应用中如何避免常见的 SQL 性能问题

Spring Boot 本身并没有直接干预 SQL 的执行,但它提供的 ORM 框架(如 Spring Data JPA, Mybatis)以及数据库连接管理,都对我们编写高效 SQL 有着重要影响。以下是一些常见的 SQL 性能问题以及在开发中我们如何避免它们:

1. 使用 SELECT *

  • 问题: SELECT * 会检索表中的所有列。这会增加数据库的网络传输量、内存消耗,并可能导致 ORM 框架在映射结果时做更多额外的工作。此外,如果表结构发生变化(例如增加列),可能会影响到不需要这些新列的查询。

  • 避免方法:

    • 明确指定需要的列: 在你的 JPA 查询(@Query 或 Criteria API)或 Mybatis XML/注解中,只选择你真正需要的列。
    • DTO/Projection: 对于只读场景,可以使用 DTO(Data Transfer Object)或 JPA 的 Projections 来指定返回的字段,避免映射整个实体。
    • Mybatis <resultMap> 在 Mybatis 中,通过 <resultMap> 精确定义列到 Java 对象的映射。

2. 使用 NOT IN 子查询(特别是大数据集时)

  • 问题:NOT IN 的子查询返回大量数据时,数据库可能会对子查询的结果集进行全表扫描,导致性能急剧下降。

  • 避免方法:

    • 使用 NOT EXISTS NOT EXISTS 通常比 NOT IN 在处理大数据集时更有效,因为它在找到第一个匹配项后就会停止搜索。
    • 使用 LEFT JOINWHERE ... IS NULL 通过 LEFT JOIN 将需要排除的表关联起来,然后在 WHERE 子句中过滤掉右表不匹配的记录。
    • 重构业务逻辑: 考虑是否有其他方式来实现相同的业务需求,避免使用这种低效的查询模式。
    • 索引优化: 确保相关字段上有合适的索引。

3. 隐式类型转换

  • 问题: 当查询条件中的数据类型与数据库列的数据类型不一致时,数据库可能会进行隐式类型转换。这会导致数据库无法使用该列上的索引,从而进行全表扫描。例如,查询条件是字符串,但数据库列是数字类型。

  • 避免方法:

    • 保持数据类型一致: 在你的 Spring Data JPA 实体类字段、Mybatis 参数和数据库列定义中,保持数据类型的一致性。
    • 显式转换: 如果必须进行类型转换,在 SQL 中使用数据库提供的类型转换函数(如 CASTCONVERT),但要注意这仍然可能影响索引的使用。
    • Spring Data JPA 类型转换: Spring Data JPA 会尝试进行类型转换,但务必确保实体属性类型与数据库列类型匹配。
    • Mybatis 类型处理器(TypeHandler): 在 Mybatis 中,可以使用 TypeHandler 来处理不同数据类型之间的转换,确保查询参数以正确的类型传递给数据库。

4. 缺少合适的索引或索引失效

  • 问题: 索引是提高查询性能的关键。缺少索引或不当的索引使用会导致数据库进行全表扫描。

  • 避免方法:

    • 分析查询语句: 使用数据库的 EXPLAINANALYZE 命令来分析查询的执行计划,了解是否使用了索引以及如何使用。
    • 为经常用于 WHERE 子句、JOIN 条件、ORDER BYGROUP BY 子句的列创建索引。
    • 复合索引的顺序: 复合索引中列的顺序很重要,应该将最常用的过滤条件放在前面。
    • 避免在 WHERE 子句中对索引列进行函数操作或计算: 这会导致索引失效。例如,WHERE UPPER(column) = 'VALUE' 就不会使用 column 上的索引。
    • 注意模糊查询 (LIKE '%value%'): 前导 % 会导致大多数索引失效。考虑使用全文索引或重构搜索逻辑。
    • 定期审查和清理不使用的索引。

5. 在循环中执行数据库操作 (N+1 查询问题)

  • 问题: 在一个主查询之后,针对每一条结果再执行一个或多个额外的查询,导致大量的数据库交互,严重影响性能。这在 ORM 框架中尤为常见。

  • 避免方法 (Spring Data JPA):

    • 使用 JOIN FETCH@EntityGraph (FetchType.JOIN): 在主查询中一次性加载关联的数据。
    • 使用 @BatchSize 对于懒加载的集合,可以设置批量加载的大小,减少查询次数。
    • 使用 DTO/Projection: 如果只需要部分关联数据,可以使用 DTO 或 Projections 来优化查询。
  • 避免方法 (Mybatis):

    • 使用 <association><collection> 标签的 select 属性进行一次性加载关联数据。
    • 使用 resultMap 定义复杂的映射关系,避免后续的懒加载。

6. 大量数据的分页和排序问题

  • 问题: 对大量数据进行分页和排序,如果没有合适的索引,性能会非常差。特别是深分页 (LIMIT offset, size),数据库需要扫描大量不需要的数据。

  • 避免方法:

    • 使用合适的索引: 确保排序字段和过滤字段上有索引。
    • 优化分页:
      • 使用书签/游标(Seek Method): 基于上次查询结果的某个唯一标识符进行分页,而不是使用 OFFSET
      • 限制分页深度: 避免用户请求过深的页码。
      • 考虑数据汇总或缓存: 对于需要频繁访问的大量数据,可以考虑进行预处理或缓存。
    • 避免在没有索引的列上进行 ORDER BY

7. 事务控制不当

  • 问题: 过长的事务会占用数据库资源,降低并发性能,并增加死锁的风险。

  • 避免方法:

    • 保持事务的原子性和尽可能短: 只包含必要的数据库操作。
    • 合理设置事务的隔离级别: 根据业务需求选择合适的隔离级别,避免不必要的锁冲突。
    • 避免在事务中进行耗时的非数据库操作(例如网络请求)。

8. 不必要的数据库操作

  • 问题: 执行了不必要的查询或更新操作,浪费了数据库资源和网络带宽。

  • 避免方法:

    • 仔细分析业务逻辑,只执行必要的数据库操作。
    • 利用 ORM 框架的缓存机制(例如 JPA 的二级缓存或 Mybatis 的缓存)。
    • 批量操作: 对于需要插入、更新或删除多条记录的情况,使用批量操作可以显著减少数据库交互次数。Spring Data JPA 提供了 saveAll()deleteAll() 等方法,Mybatis 可以通过 <foreach> 标签实现批量操作。
    • 避免在循环中进行数据库写入操作。

9. 动态 SQL 构建不当

  • 问题: 如果动态 SQL 构建不当,可能会产生低效的 SQL 语句,甚至导致 SQL 注入安全风险。

  • 避免方法 (Spring Data JPA):

    • 使用 Criteria API 或 Specification: 这些 API 可以安全地构建动态查询。
    • 使用 @Query 并结合 SpEL 表达式进行条件判断,但要注意安全性。
  • 避免方法 (Mybatis):

    • 使用 <if>, <where>, <set>, <foreach> 等动态 SQL 标签来安全地构建 SQL 语句。
    • 使用 # 占位符而不是 $ 进行参数传递,防止 SQL 注入。

10. 数据库连接管理不当

  • 问题: 不合理的数据库连接池配置可能导致连接不足或过多的开销。

  • 避免方法 (Spring Boot):

    • 合理配置 spring.datasource 相关属性,例如连接池大小、最大连接数、最小空闲连接数、连接超时时间等。
    • 使用 HikariCP 等高性能的连接池。Spring Boot 默认使用 HikariCP。
    • 确保数据库连接在使用完毕后能够正确释放。Spring Boot 的数据源管理和事务管理通常会处理这个问题。

总结:

避免 Spring Boot 应用中的 SQL 性能问题需要对 SQL 优化原则有一定的了解,并且熟悉你所使用的 ORM 框架的特性。

  • 理解你的数据模型和业务需求。
  • 编写清晰、简洁、只获取必要数据的 SQL 语句。
  • 合理地使用索引。
  • 避免 N+1 查询等常见的 ORM 陷阱。
  • 关注数据库的执行计划,及时发现和解决性能问题。
  • 进行充分的性能测试,验证你的优化措施是否有效。
http://www.dtcms.com/a/121706.html

相关文章:

  • C++学习之套接字并发服务器
  • 砍树(二分)
  • 搜广推校招面经七十一
  • 示波器直流耦合与交流耦合:何时使用哪种?
  • Spring Boot 中集成 Knife4j:解决文件上传不显示文件域的问题
  • [漏洞篇]SSRF漏洞详解
  • 华为网路设备学习-17
  • 即时通讯软件BeeWorks,企业如何实现细粒度的权限控制?
  • PostgreSQL-数据库的索引 pg_operator_oid_index 损坏
  • JAVAWeb_Servlet:前置准备与理论简易介绍
  • input_ids ,attention_mask 是什么
  • js解除禁止复制、禁止鼠标右键效果
  • 阿里发布实时数字人项目OmniTalker,实时驱动技术再突破~
  • json 转 txt 用于 yolo 训练(可适用多边形标注框_python)
  • HOW - React Developer Tools 调试器
  • SpringBoot和微服务学习记录Day1
  • 决策树+泰坦尼克号生存案例
  • 强化学习原理一
  • 本地部署 opik
  • 卡码网54.替换数字
  • 紫光展锐5G SoC T8300:影像升级,「定格」美好世界
  • 用户画像(https://github.com/memodb-io/memobase)应用
  • 神经网络 - 关于简单的激活函数的思考总结
  • Java-对比两组对象找出发生变化的字段工具-支持枚举映射-支持时间-支持显示对应字段中文描述-嵌套list等场景
  • 淘宝API接口:淘宝API接口概述以及对开发者的具体帮助
  • SSRF漏洞技术解析与实战防御指南
  • 【Git 常用操作指令指南】
  • 汽车车窗升降系统全生命周期耐久性验证方案研究
  • Git 的进阶功能和技巧
  • git强制更新本地分支