Node.js连接Oracle数据库时DELETE删除重复记录卡住无响应
问题表现
- 开发过程中误重复发送两次携带相同insert语句的请求,导致相同记录被重复插入2次
- 执行DELETE语句删除对应重复记录时,请求一直卡在
sending request状态无法推进,问题截图:
- 测试验证规律:删除无重复值的记录(例如request_id为1002的非重复记录)时,DELETE操作可正常完成;删除存在重复值的记录(例如request_id为1001的2条重复记录)时,请求会一直卡住无响应
- 涉及的接口实现代码如下:
var express=require('express'); var router=express.Router(); var oracledb=require('oracledb'); router.post('/ins-rpt',function(req,res,next){ //get the data from req const {stat,rid,rptcomp,reqr}=req.body; //connect with db var connectionString="(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = localhost)(PORT = 1521))(CONNECT_DATA =(SERVER = DEDICATED)(SERVICE_NAME = orcl))" oracledb.getConnection( { user: 'system', password:'Valli1234', tns:connectionString }, async function(err,con){ if(err){ res.status(500).json({ message : 'not connected' }) } else{ //reqd opn var q="delete from rpt where request_id=:1"; //send response await con.execute(q,[], {autoCommit:true},function(e,s){ if(e){ res.status(500).json({ message : e }) } else { res.status(200).json({ message : s }) } }) } }); }) module.exports=router;
问题原因
- 核心阻塞原因是Oracle行锁等待:之前两次重复插入的请求,存在未正常提交/回滚的事务,这个事务一直持有那两条重复记录的行级排他锁。当新发起DELETE请求要删除这两条记录时,必须等持有锁的事务提交/回滚释放锁才能继续执行,所以会一直挂起。而测试的非重复记录是已经正常提交的,没有未释放的锁,所以删除可以正常完成。
- 代码本身存在3个明显bug,会放大这类问题:
- DELETE语句用了绑定变量
:1,但执行时传入的绑定参数是空数组[],根本没有传入要删除的request_id值 - 混用了
await和callback写法:oracledb的execute方法传入回调函数时不会返回Promise,加await完全无效,会导致事务状态异常、连接泄漏 - 操作完成后没有调用
con.close()释放数据库连接,连接泄漏到一定程度会直接导致所有后续数据库请求卡住
- DELETE语句用了绑定变量
修复步骤
- 先处理当前的锁阻塞,登录Oracle执行以下SQL找到持有锁的会话,杀掉阻塞会话即可释放锁,之后DELETE操作就能正常执行:
-- 查询RPT表上的锁对应会话 SELECT s.sid, s.serial# FROM v$locked_object l, dba_objects o, v$session s WHERE l.object_id = o.object_id AND l.session_id = s.sid AND o.object_name = 'RPT'; -- 替换成上面查询到的sid和serial#,杀掉阻塞会话 ALTER SYSTEM KILL SESSION 'sid,serial#';
- 修复接口代码问题,统一用async/await写法,保证连接正确释放、参数正确传入,参考写法:
const express = require('express'); const router = express.Router(); const oracledb = require('oracledb'); router.post('/ins-rpt', async function(req, res, next) { const { stat, rid, rptcomp, reqr } = req.body; let con; const connectionString = "(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))(CONNECT_DATA =(SERVER = DEDICATED)(SERVICE_NAME = orcl))"; try { con = await oracledb.getConnection({ user: 'system', password: 'Valli1234', tns: connectionString }); // 绑定参数位置传要删除的request_id,这里用rid对应从请求体取的参数,按实际业务调整即可 const deleteSql = "delete from rpt where request_id = :1"; const result = await con.execute(deleteSql, [rid], { autoCommit: true }); res.status(200).json({ message: result }); } catch (err) { res.status(500).json({ message: err.message || '数据库操作失败' }); } finally { // 无论操作成功失败,都必须释放数据库连接 if (con) await con.close(); } }); module.exports = router;
- 长期避免重复插入问题,建议给
request_id字段加唯一约束,从数据库层面直接拦截重复数据,不用靠业务逻辑兜底。
内容的提问来源于stack exchange,提问作者Valli VK
相关产品推荐
相关产品推荐

