【问题标题】:Why is my Spring Data JPA query 8 times slower than Node.JS + oracledb?为什么我的 Spring Data JPA 查询比 Node.JS + oracledb 慢 8 倍?
【发布时间】:2019-11-20 14:30:22
【问题描述】:

我创建了两个基本项目来比较框架。在对最近的工作项目进行开发时,我注意到使用 Spring Data JPA 时查询运行速度非常慢。

我设置了一个小实验来测试 NodeJS 与 Spring Boot,以确定它是数据库还是框架。

SELECT * FROM v$version;

Oracle Database 12c 企业版 12.1.0.2.0 - 64 位生产

该数据库位于 400 英里外的另一个设施中,会引入大约 60-80 毫秒的网络延迟。

-------------- -------- ------------ 
ID             NOT NULL NUMBER       
AR                      VARCHAR2(10) 
MOD_TIME                DATE         
MOD_UID                 VARCHAR2(10) 
ACTIVE_IND              VARCHAR2(1)  
WORK_ID                 NUMBER       

在我们的测试环境中,这个数据库中有 4533 条记录。我们有大约 9000 个在生产中。本实验将使用测试环境运行。

弹簧设置:

start.spring.io 并选择 Web ,JPA, Oracle Driver, lombok

创建了一个实体类

@Entity
@Table(name = "t_test")
@Data
public class TTest implements Serializable {

  private static final long serialVersionUID = 3305605889880335034L;

  @Id
  @Column(name = "ID")
  private int id;

  @Column(name = "AR")
  private String ar;

  @Column(name = "mod_time")
  private Timestamp modTime;

  @Column(name = "mod_uid")
  private String modId;

  @Column(name = "active_ind")
  private String activeInd;

  @Column(name = "work_id")
  private Integer wid;

}

然后是一个简单的存储库来运行 findAll() 查询

@Repository
public interface TTestRepo extends JpaRepository<TTest, Integer> {}

最后是控制器

@RestController
@Slf4j
public class TestController {

    @Autowired
    TTestRepo repo;

    @GetMapping("/testDb")
    public List<TTest> testDb(){
        return repo.findAll();
    }

}

我使用 application.properties 连接到数据库

spring.datasource.url=blah
spring.datasource.username=blah
spring.datasource.password=blah
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG

NodeJS 设置

只是一个安装了 oracledb 的简单快速应用程序。

const express = require('express')

var oracledb = require('oracledb')
oracledb.getConnection(
  {
    //removed for obvious reasons
  },
  function(err, connection) {
    console.log('trying to connect...')
    if (err) {
      console.error(err)
      return
    }

    global.connection = connection
  }
)

global.transformResults = function transformResults(result) {
  let finalResults = []
  let obj = {}

  result.rows.forEach((row) => {
    result.metaData.forEach( (meta, j) => {
      obj[meta.name] = row[j]
    })
    finalResults.push(obj)
    obj = {}
  })
  return finalResults
}

// Create express instnace
const app = express()

// Require API routes
const users = require('./routes/users')

// Import API Routes
app.use(users)

// Export the server middleware
module.exports = {
  path: '/api',
  handler: app
}

users.js 只是我运行查询的路由器或休息端点

const { Router } = require("express");
const router = Router();

router.get("/testDb", async function(req, res, next) {
    connection.execute(
      "SELECT * from t_test",
      function(err, result) {
        if (err) {
          console.error(err)
          return
        }
        res.json(transformResults(result));
      }
    )
});

module.exports = router;

基准测试

对于 Spring Data JPA,我做了这个基准测试

/**
 * @author Jake Perkins on 11/20/2019
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class BenchMark {

    @Autowired
    TestController controller;

    @Test
    public void benchmarkDb(){
        int testLength = 100;
        long startTime = System.currentTimeMillis();
        for(int i = 0; i < testLength; i++){
            controller.testDb();   //Measure execution time for this method
        }
        long endTime = System.currentTimeMillis();
        long durationInMillis = (endTime - startTime);   //Total execution time in milliseconds
        BigDecimal averageInSeconds = BigDecimal.valueOf(durationInMillis/testLength).movePointLeft(3);
        System.out.println(averageInSeconds);
    }

}

输出:

23.463

NodeJS 基准测试是使用以毫秒为单位的开始时间和以毫秒为单位的结束时间之间的差异进行类似计算的。

实验结果

我在两种环境中都运行了 100 次查询,并收集了以下平均时间。

Spring Boot:23.4 秒

NodeJS:2.9 秒

Oracle SQL Developer:2.6 秒

在收集 4533 条记录时(在我的具体情况下),Spring Boot 所用的时间大约是节点 JS 的 8 倍。为什么?

【问题讨论】:

  • 能否提供您确定基准时间的代码
  • 你试过直接使用 JDBC 吗? Spring 和 Hibernate 使用反射/代理将数据转换回来。此外,这是针对单个查询还是您尝试在同一个 JVM 中多次运行查询(以确保预热)?
  • @DanW 不,我没有尝试过直接的 JDBC,但是这个问题具体与 Spring Data JPA 有关。是的,我在同一个 JVM 中多次运行此查询。这个查询实际上是我在工作中创建的应用程序的重要组成部分。我已经运行了这个查询数百次(如果不是数千次)。这个实验的灵感来自于我每次测试我的应用程序界面都会等待很长时间。
  • 尝试分享 Spring Data 的基准测试代码。根据我的经验,这种行为根本不正常。
  • nodejs 上没有缓存也没有水合。虽然有标准的 jpa 设置。但仍然不应该产生那么大的延迟。

标签: java node.js spring-boot express spring-data-jpa


【解决方案1】:

我的第一个想法是数组获取大小或预取大小的差异。这会对 WAN 上的多行查询性能产生重大影响。

来自Oracle® Database JDBC Developer's Guide

默认情况下,当 Oracle JDBC 运行查询时,它会检索结果集 来自数据库游标的一次 10 行

来自node-oracledb Documentation

此属性设置用于获取的内部缓冲区的大小 从 Oracle 数据库查询行。更改它可能会影响查询 性能但不影响返回多少行 应用。

默认值为 100。

您可以在 Node.js 应用程序中轻松地将 oracledb.fetchArraySize 更改为 10,看看性能是否会下降到 Spring 的水平。

而且你可以增加尺寸,看看你是否能获得更好的性能。

【讨论】:

  • 就是这样!我在 Spring Boot 中创建了一个 DataSource bean,并添加了以下属性。 properties.put("defaultRowPrefetch", "100"); properties.put("defaultBatchValue", "100"); 我的平均时间从 30 秒变为 3.8 秒。最后一个问题...我已经以 1000 作为设置对此进行了测试,并且能够达到 2.1 秒。将此值设置得如此之高会产生影响吗?我们希望此表会增长许多倍于当前大小,并希望保持选择查询的速度非常快。
【解决方案2】:

我对 Spring Boot 一无所知,但我希望所有选项的性能都比您所看到的更接近。 Spring Boot 是否默认创建连接池?如果没有,你应该调查一下。

此外,Node.js 代码正在重用单个连接。您应该改用连接池。有关更多信息,请参阅有关使用 Node.js 和 Oracle 数据库创建 REST API 的本系列: https://jsao.io/2018/03/creating-a-rest-api-with-node-js-and-oracle-database/

【讨论】:

  • 是的,Spring Boot 使用 HikariCP 作为默认连接池。感谢您的参考,因为您可以告诉我我是 NodeJS Rest API 的新手。我只是想看看另一边的草是否更绿。通过使用连接池,我会看到 NodeJS 的性能差异吗?
  • 性能本身并不会更好,但是连接池提供了如果你只是绕过一个连接就无法获得的各种功能。您将如何创建事务边界、在使用连接时对请求进行排队等?不要忘记简单的execute 调用只是开始。有许多类型的调用,例如流式传输大型结果集和需要许多后续调用才能完成的 LOB。
  • 连接池将使应用程序具有可扩展性。
猜你喜欢
  • 2020-04-05
  • 2017-10-16
  • 2016-07-29
  • 2017-01-21
  • 1970-01-01
  • 1970-01-01
  • 2022-09-29
  • 1970-01-01
  • 2015-02-20
相关资源
最近更新 更多