为什么我的 Spring Data JPA 查询比 Node.JS + oracledb 慢 8 倍?

Jak*_*ins 6 java node.js express spring-data-jpa spring-boot

我创建了两个基本项目来比较框架。在最近的工作项目上进行开发时,我注意到使用 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       
Run Code Online (Sandbox Code Playgroud)

在我们的测试环境中,该数据库中有 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;

}
Run Code Online (Sandbox Code Playgroud)

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

@Repository
public interface TTestRepo extends JpaRepository<TTest, Integer> {}
Run Code Online (Sandbox Code Playgroud)

最后是一个控制器

@RestController
@Slf4j
public class TestController {

    @Autowired
    TTestRepo repo;

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

}

Run Code Online (Sandbox Code Playgroud)

我使用 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
Run Code Online (Sandbox Code Playgroud)

NodeJS 设置

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

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
}

Run Code Online (Sandbox Code Playgroud)

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;

Run Code Online (Sandbox Code Playgroud)

基准测试

对于 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

输出:

23.463
Run Code Online (Sandbox Code Playgroud)

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

实验结果

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

Spring Boot:23.4 秒

NodeJS:2.9 秒

Oracle SQL 开发人员:2.6 秒

在收集 4533 条记录(在我的特定情况下)时,Spring Boot 花费的时间大约是 node JS 的 8 倍。为什么?

Chr*_*nes 3

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

\n\n

来自Oracle\xc2\xae 数据库 JDBC 开发人员指南

\n\n
\n

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

\n
\n\n

来自node-oracledb文档

\n\n
\n

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

\n\n

默认值为 100。

\n
\n\n

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

\n\n

您可以增加大小,看看是否可以获得更好的性能。

\n

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