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

时间:2017-04-04 23:22:25

标签: angularjs node.js oracledb oracledb-npm

我目前正在开展一个项目,我有一个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;

1 个答案:

答案 0 :(得分:3)

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

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

我会指出一些可以帮助提高表现的事情,但并非所有事情都与这个问题完全相关。

首先,如果您使用的是Web服务器,则应该使用连接池,而不是专用/一次性连接。通常,您将在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;
}

我希望有所帮助。如果您有疑问,请告诉我。