从 NodeJS 查询 Oracle 数据库中的大型数据集

Posted

技术标签:

【中文标题】从 NodeJS 查询 Oracle 数据库中的大型数据集【英文标题】:Querying Large Dataset in Oracle Database from NodeJS 【发布时间】:2017-08-30 08:52:19 【问题描述】:

我目前正在从事一个项目,我有一个 Oracle 10 数据库表,大约 310K 给或取 10-30K 行。

目标是在有角度的前端显示这些行,但是通过 NodeJS 返回所有这些行需要很多时间。

鉴于我是第一次同时使用 NodeJS 和 oracledb,我假设我一定错过了什么?

var oracledb = require('oracledb');
var config = require(__dirname+'/../db.js');

function get(req,res,next)

var table = req.query.table;
var meta;

oracledb.getConnection(config.oracle)
.then( function(connection)

    var stream = connection.queryStream('SELECT * FROM '+table);

    stream.on('error', function (error) 
    
        console.error(error);
        return next(err);
    );

    stream.on('metadata', function (metadata) 
        console.log(metadata);
    );

    stream.on('data', function (data) 
        console.log(data);
    );

    stream.on('end', function () 
    
      connection.release(
        function(err) 
          if (err) 
            console.error(err.message);
            return next(err);
          
        );
    );
)
.catch(function(err)
    if(err)
        connection.close(function(err)
            if(err)
                console.error(err.message);
                return next(err);
            
        );
    
)


module.exports.get = get;

【问题讨论】:

请原谅,我真的不知道 NodeJs,但是 sql 位...是否需要一次在页面上显示所有 300k+ 行?使用分页控件每页拉 10k 就足够了吗?不带过滤器的 SELECT * 几乎总是严重的矫枉过正。如果我知道页面的意图,我可能会帮助提出一些建议。不幸的是,我真的不认为对于未定义列长度的 300k+ 查询的加载时间有什么可做的。我已经被证明是错误的,可以说我正在关注这个,以防有人回答。 这正是我将通过使用智能表组件在前端的角度部分所做的,但我想尽可能地限制服务器端的请求。所以我的想法是,在用户浏览器本地存储中加载所有数据(此时它大约是 30mb - google chrome 吃掉了 100 倍),然后使用 angular 在客户端进行计算。然而,在我的请求超时之前获取所有记录被证明是非常困难的。 【参考方案1】:

30 MB 是要加载到前端的大量数据。它可以在某些情况下工作,例如桌面 Web 应用程序,其中“缓存”数据的好处抵消了加载它所需的时间(并且增加陈旧数据是可以的)。但它在其他情况下效果不佳,例如移动设备。

请记住,必须将 30 MB 从 DB 移动到 Node.js,然后从 Node.js 移动到客户端。它们之间的网络连接将极大地影响性能。

我将指出一些有助于提高性能的事情,但并非所有事情都与这个问题完全相关。

首先,如果您使用的是网络服务器,则应该使用连接池,而不是专用/一次性连接。通常,您会在 index/main/app.js 中创建连接池,并在完成并准备好之后启动 Web 服务器。

这是一个例子:

const oracledb = require('oracledb');
const express = require('express');
const config = require('./db-config.js');
const thingController = require('./things-controller.js');

// Node.js used 4 background threads by default, increase to handle max DB pool.
// This must be done before any other calls that will use the libuv threadpool.
process.env.UV_THREADPOOL_SIZE = config.poolMax + 4;

// This setting can be used to reduce the number of round trips between Node.js
// and the database.
oracledb.prefetchRows = 10000;

function initDBConnectionPool() 
  console.log('Initializing database connection pool');

  return oracledb.createPool(config);


function initWebServer() 
  console.log('Initializing webserver');

  app = express();

  let router = new express.Router();

  router.route('/things')
    .get(thingController.get);  

  app.use('/api', router);

  app.listen(3000, () => 
    console.log('Webserver listening on localhost:3000');
  );


initDBConnectionPool()
  .then(() => 
    initWebServer();
  )
  .catch(err => 
    console.log(err);
  );

这将创建一个添加到驱动程序中的internal pool cache 的池。这使您可以轻松地从其他模块访问它(稍后示例)。

请注意,在使用连接池时,通常最好增加 Node.js 可用的线程池,以允许池中的每个连接同时工作。上面有一个例子。

另外,我正在增加oracledb.prefetchRows 的值。此设置与您的问题直接相关。网络往返用于在 DB 和 Node.js 之间移动数据。此设置允许您调整每次往返获取的行数。因此,随着 prefetchRows 越高,需要的往返次数越少,性能也会提高。请注意不要根据您的 Node.js 服务器中的内存进行过高处理。

我运行了一个模拟 30 MB 数据集大小的通用测试。当 oracledb.prefetchRows 保留默认值 100 时,测试在 1 分 6 秒内完成。当我将它提高到 10,000 时,它在 27 秒内完成。

好的,继续讨论基于您的代码的“things-controller.js”。我已更新代码以执行以下操作:

断言该表是一个有效的表名。您当前的代码容易受到 SQL 注入的攻击。 使用模拟 try/catch/finally 块的 Promise 链仅关闭一次连接并返回遇到的第一个错误(如果需要)。 工作,以便我可以运行测试。

结果如下:

const oracledb = require('oracledb');

function get(req, res, next) 
    const table = req.query.table;
    const rows = [];
    let conn;
    let err; // Will store the first error encountered

    // You need something like this to preven SQL injection. The current code
    // is wide open.
    if (!isSimpleSqlName(table)) 
        next(new Error('Not simple SQL name'));
        return;
    

    // If you don't pass a config, the connection is pulled from the 'default'
    // pool in the cache.
    oracledb.getConnection() 
        .then(c => 
            return new Promise((resolve, reject) => 
                conn = c;

                const stream = conn.queryStream('SELECT * FROM ' + table);

                stream.on('error', err => 
                    reject(err);
                );

                stream.on('data', data => 
                    rows.push(data); 
                );

                stream.on('end', function () 
                    resolve();
                );
            );
        )
        .catch(e => 
            err = err || e;
        )
        .then(() => 
            if (conn)  // conn assignment worked, need to close/release conn
                return conn.close();
            
        )
        .catch(e => 
            console.log(e); // Just log, error during release doesn't affect other work
        )
        .then(() => 
            if (err) 
                next(err);
                return;
            

            res.status(200).json(rows);
        );


module.exports.get = get;

function isSimpleSqlName(name) 
  if (name.length > 30) 
    return false;
  

  // Fairly generic, but effective. Would need to be adjusted to accommodate quoted identifiers,
  // schemas, etc.
  if (!/^[a-zA-Z0-9#_$]+$/.test(name)) 
    return false;
  

  return true;

我希望这会有所帮助。如果您有任何问题,请告诉我。

【讨论】:

感谢您的汇集建议,我实施了您建议的方法,它在性能方面帮助了我很多。我现在在 24 秒内收集 40mb。这只是一个网络浏览器应用程序,所以我认为这不会成为问题,并且表变量将通过查询从数据库中提取出来,以便我能够通过邮递员请求更改查询所以我每当我想测试不同的表时,不必不断地更改字符串。流式传输是否也是一个可行的解决方案,比如 socket.io,我使用 angularjs 作为前端? 观看我在最近一次活动中谈到“实时”数据的视频。 cmets中有幻灯片的链接和代码:youtube.com/watch?v=xRJLcLoLBq8 我有:index.js 在那里我完全喜欢你建议的关于初始化的方法。然后我有 data.js,我将其描述为一个问题,我再次修改为您描述的方法。在 index.js 文件中,我像这样路由它们: var data = require(__dirname+'/routes/data.js');并在 initWebServer() 函数中: var router = express.Router(); // morgan, body parser, cors router.post('/data', auth(), data.get); app.use('/api', 路由器);我可以以某种方式将 socket.io 附加到 data.get 以便我可以从那里发送吗? 我不确定我是否遵循。需要将客户端设置为接受来自 Socket.io 的事件(基于事件)。这通常与 REST 的工作方式(基于请求/响应)不兼容。 我想在这里从 queryStream 发出一个发射:stream.on('data', data => rows.push(data); //而不是这个 io.stream.emit('新行',数据););

以上是关于从 NodeJS 查询 Oracle 数据库中的大型数据集的主要内容,如果未能解决你的问题,请参考以下文章

Oracle数据库中的大对象(LOB)数据类型介绍

结果集需要很长时间来处理来自 Oracle 的大数据

从nodejs查询MongoDB中的日期范围

允许谷歌大查询中的大结果

oracle数据库如何查询表数据量

如何从 Node Js 服务器中的查询字符串中获取 id 或任何变量数据?