从 NodeJS 查询 Oracle 数据库中的大型数据集
Querying Large Dataset in Oracle Database from NodeJS
我目前正在从事一个项目,我有一个 Oracle 10 数据库 table,大约有 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 移动到客户端。这些之间的网络连接将极大地影响性能。
我会指出一些有助于提高性能的事情,尽管并非所有事情都与这个问题完全相关。
首先,如果您使用的是 Web 服务器,则应该使用连接池,而不是 dedicated/one-off 连接。通常,您会在 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",它基于您的代码。我更新了代码以执行以下操作:
- 断言 table 是一个有效的 table 名称。您当前的代码易受 SQL 注入攻击。
- 使用模拟 try/catch/finally 块的承诺链仅关闭一次连接并 return 遇到第一个错误(如果需要)。
- 工作所以我可以 运行 考试。
结果如下:
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;
}
希望对您有所帮助。如果您有任何问题,请告诉我。
我目前正在从事一个项目,我有一个 Oracle 10 数据库 table,大约有 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 移动到客户端。这些之间的网络连接将极大地影响性能。
我会指出一些有助于提高性能的事情,尽管并非所有事情都与这个问题完全相关。
首先,如果您使用的是 Web 服务器,则应该使用连接池,而不是 dedicated/one-off 连接。通常,您会在 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",它基于您的代码。我更新了代码以执行以下操作:
- 断言 table 是一个有效的 table 名称。您当前的代码易受 SQL 注入攻击。
- 使用模拟 try/catch/finally 块的承诺链仅关闭一次连接并 return 遇到第一个错误(如果需要)。
- 工作所以我可以 运行 考试。
结果如下:
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;
}
希望对您有所帮助。如果您有任何问题,请告诉我。