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

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

目标是在angular度前端显示这些行,但通过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; 

30 MB是大量的数据加载到前端。 它可以在某些情况下工作,如桌面Web应用程序,其中“caching”数据的好处抵消了加载它所需的时间(并且增加陈旧的数据是可以的)。 但在其他情况下,如移动设备,这种方式将无法正常工作。

请记住,30 MB必须从数据库移到Node.js,然后从Node.js移到客户端。 这些networking连接将会对性能产生很大的影响。

我会指出一些有助于performance的事情,但并不是所有的事情都与这个问题完全相关。

首先,如果您使用的是Web服务器,则应该使用连接池,而不是专用/一次性连接。 一般来说,你可以在你的index / main / app.js中创build连接池,并在完成并准备好之后启动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); }); 

这将创build一个池,将其添加到驱动程序的内部池caching中。 这使您可以轻松地从其他模块访问它(例如,稍后)。

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

另外,我正在增加oracledb.prefetchRows的值。 这个设置直接关系到你的问题。 networking往返用于在数据库和Node.js之间移动数据。 此设置允许您调整每次往返取的行数。 因此,预取行数越高,需要的往返次数越less,性能也越高。 只要注意不要因为Node.js服务器中的内存太高而丢失。

我运行了一个嘲笑30 MB数据集大小的通用testing。 当oracledb.prefetchRows的默认值为100时,testing在1分6秒内完成。 当我把它撞到10000时,它在27秒内完成。

好的,转到基于你的代码的“things-controller.js”。 我已经更新了代码来执行以下操作:

  • 断言该表是一个有效的表名。 您当前的代码容易受到SQL注入的影响。
  • 使用模拟try / catch / finally块的promise链只closures连接一次,并返回遇到的第一个错误(如果需要)。
  • 工作,我可以运行testing。

结果如下:

 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; } 

我希望有帮助。 如果你有问题,请告诉我。