Node.js中Oracle连接池触发NJS-040超时错误求助
解决Node.js应用中node-oracledb连接池NJS-040超时错误
问题背景
使用Oracle客户端19.20.0.0.0与node-oracledb 6.1.0开发Node.js应用,采用连接池结合中间件的方式获取数据库连接,应用随机抛出如下错误:
Error: NJS-040: connection request timeout. Request exceeded "queueTimeout" of 60000
相关代码实现及错误日志如下:
连接池初始化及中间件代码
const oracledb = require('oracledb'); const logger = require('../logger'); const { PASSWORD, USERNAME, CONN_STRING } = require('../constant'); oracledb.initOracleClient(); oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT; async function initializeOraclePool() { try { await oracledb.createPool({ user: USERNAME, password: PASSWORD, connectionString: CONN_STRING, poolMax: 8, poolMin: 1, poolTimeout: 2000000, }); logger.info(`Oracle client version is ${oracledb.oracleClientVersionString}`) } catch (error) { logger.error(error) console.error('Error creating Oracle connection pool:', error); } } // Middleware to acquire a database connection from the pool async function getOracleConnection(req, res, next) { try { req.connection = await oracledb.getConnection(); next(); } catch (error) { console.error('Error acquiring Oracle connection:', error); logger.error(error) res.status(500).json({ error: 'Database connection error' }); } } module.exports = { initializeOraclePool, getOracleConnection };
业务代码示例
const fetchSupplierRecommendation = async (req, res) => { try { const { connection = {}, body = {}, query = {}, params = {} } = req; let { planId, srInstanceId, categorySetId, from, to } = query const result = await connection.execute(SQLQuery.SupplierRecommendation(planId, srInstanceId, categorySetId, from, to)) successHandler(res, result.rows) } catch (error) { errorHandler(res, error) } }
错误日志
0|index | Error acquiring Oracle connection: Error: NJS-040: connection request timeout. Request exceeded "queueTimeout" of 60000 0|index | at Object.throwErr (/var/node/node_modules/oracledb/lib/errors.js:588:10) 0|index | at Timeout._onTimeout (/var/node/node_modules/oracledb/lib/pool.js:443:22) 0|index | at listOnTimeout (node:internal/timers:569:17) 0|index | at process.processTimers (node:internal/timers:512:7) { 0|index | code: 'NJS-040' 0|index | }
原因分析
- 连接未正确释放:当前代码中,从连接池获取的连接在请求处理完成后没有被释放回池,导致连接被长期占用。当所有
poolMax(当前设置为8)个连接都被占用时,新的连接请求会进入队列等待,超过默认的queueTimeout(60秒)后就会抛出NJS-040错误。 - 连接池配置不合理:
poolMax设置可能无法满足应用并发需求;未显式设置queueTimeout,使用默认值60秒,若并发请求较多,排队时间容易超过阈值。 - 慢查询占用连接:如果业务SQL执行时间过长,会导致连接长时间被占用,加剧连接池耗尽的问题。
解决方案
1. 确保连接在请求结束后释放
修改中间件,在请求处理完成(无论成功或失败)后,将连接释放回池:
async function getOracleConnection(req, res, next) { try { req.connection = await oracledb.getConnection(); // 请求结束后释放连接 res.on('finish', async () => { if (req.connection) { try { await req.connection.close(); } catch (closeErr) { logger.error('Error closing Oracle connection:', closeErr); } } }); next(); } catch (error) { console.error('Error acquiring Oracle connection:', error); logger.error(error); res.status(500).json({ error: 'Database connection error' }); } }
或者在业务代码的finally块中释放连接:
const fetchSupplierRecommendation = async (req, res) => { let connection; try { connection = req.connection; const { body = {}, query = {}, params = {} } = req; let { planId, srInstanceId, categorySetId, from, to } = query const result = await connection.execute(SQLQuery.SupplierRecommendation(planId, srInstanceId, categorySetId, from, to)) successHandler(res, result.rows) } catch (error) { errorHandler(res, error) } finally { if (connection) { try { await connection.close(); } catch (closeErr) { logger.error('Error closing connection:', closeErr); } } } }
2. 优化连接池配置
根据应用并发量调整poolMax,同时显式设置queueTimeout(根据业务容忍度调整,比如延长至120秒):
await oracledb.createPool({ user: USERNAME, password: PASSWORD, connectionString: CONN_STRING, poolMax: 16, // 根据并发量调整,比如翻倍 poolMin: 1, poolTimeout: 2000000, queueTimeout: 120000 // 显式设置排队超时时间,单位毫秒 });
3. 排查慢查询
检查SQLQuery.SupplierRecommendation生成的SQL是否存在性能问题,比如缺少索引、查询逻辑冗余等。可以通过Oracle的执行计划分析慢查询,优化SQL性能,减少连接占用时间。
4. 监控连接池状态
添加日志监控连接池的使用情况,比如获取连接、释放连接的时间,以及池的当前连接数:
// 在初始化后添加监控 const pool = await oracledb.createPool(...); setInterval(() => { const stats = pool.getStatistics(); logger.info(`Connection Pool Stats: ${JSON.stringify(stats)}`); }, 60000); // 每分钟打印一次状态
内容的提问来源于stack exchange,提问作者Abhinay Narayan Singh
相关产品推荐
相关产品推荐

