Node.js中使用MSSQL编写插入数据函数的技术咨询
我来给你梳理下这个插入函数开发过程中容易遇到的几个关键问题,以及对应的实用解决方案——都是实际项目里踩过坑后总结出来的经验:
1. SQL注入风险(最核心的安全问题)
要是直接把action和user参数拼进SQL语句里,那绝对是给SQL注入留了后门——攻击者随便构造个恶意参数,就能篡改甚至删除你的数据表数据。
解决方案:强制使用参数化查询
不管你用哪个MSSQL库(比如官方的mssql包),都得用预编译的参数化语句,让数据库自动处理参数转义,从根源上避免注入。示例代码如下:
const sql = require('mssql'); async function saveActionToDB(action, user) { if (config.Logging.DB.type !== 'mssql') return; const dbOptions = { user: config.Logging.DB.user, password: config.Logging.DB.password, server: config.Logging.DB.server, database: config.Logging.DB.database, options: { encrypt: config.Logging.DB.encrypt } }; try { // 建立连接 await sql.connect(dbOptions); // 创建请求并绑定参数 const request = new sql.Request(); request.input('action', sql.VarChar, action); request.input('user', sql.VarChar, user); // 执行参数化插入(注意user是SQL关键字,要用方括号转义) await request.query(`INSERT INTO YourTableName (action, [user]) VALUES (@action, @user)`); } catch (err) { console.error('插入数据失败:', err); } finally { // 确保连接关闭,避免资源泄漏 await sql.close(); } }
2. 频繁创建连接导致性能损耗
每次调用函数都新建数据库连接,会消耗大量服务器和数据库资源,并发高的时候甚至会把数据库连接池打满,导致后续请求超时。
解决方案:用连接池复用连接mssql库本身支持连接池,初始化一次池之后,后续调用直接从池里拿连接,用完自动归还,大幅提升性能。示例:
const sql = require('mssql'); // 全局初始化连接池(项目启动时执行一次即可) let dbPool; async function initDBPool() { const dbOptions = { user: config.Logging.DB.user, password: config.Logging.DB.password, server: config.Logging.DB.server, database: config.Logging.DB.database, options: { encrypt: config.Logging.DB.encrypt }, pool: { max: 10, // 最大连接数,根据业务调整 min: 2, // 最小空闲连接数 idleTimeoutMillis: 30000 // 连接空闲超时时间(毫秒) } }; dbPool = await sql.connect(dbOptions); } // 项目启动时初始化连接池 initDBPool().catch(err => console.error('连接池初始化失败:', err)); async function saveActionToDB(action, user) { if (config.Logging.DB.type !== 'mssql' || !dbPool) return; try { const request = dbPool.request(); request.input('action', sql.VarChar, action); request.input('user', sql.VarChar, user); await request.query(`INSERT INTO YourTableName (action, [user]) VALUES (@action, @user)`); } catch (err) { console.error('插入数据失败:', err); } }
3. 缺乏错误处理与日志记录
如果插入过程中出现网络波动、数据库权限不足、字段约束冲突等问题,没有错误处理的话程序会直接崩溃,而且你根本不知道哪里出了问题。
解决方案:完善try/catch捕获+专业日志记录
除了捕获错误,最好用专业日志库(比如winston、pino)把错误信息详细记录下来,包括时间、传入的参数、错误栈,方便后续排查:
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [new winston.transports.File({ filename: 'db-operations.log' })] }); async function saveActionToDB(action, user) { if (config.Logging.DB.type !== 'mssql' || !dbPool) return; try { const request = dbPool.request(); request.input('action', sql.VarChar, action); request.input('user', sql.VarChar, user); const result = await request.query(`INSERT INTO YourTableName (action, [user]) VALUES (@action, @user)`); logger.info(`成功插入操作记录,影响行数: ${result.rowsAffected[0]}`, { action, user }); } catch (err) { logger.error('插入操作记录失败', { timestamp: new Date().toISOString(), action, user, errorMessage: err.message, errorStack: err.stack }); // 可以根据业务需求决定是否抛出错误,让上层逻辑处理 // throw err; } }
4. 参数合法性校验缺失
如果传入的action是空字符串、user是undefined,插入空数据会导致数据表数据质量下降,甚至触发字段非空约束报错。
解决方案:先做参数校验再执行插入
在函数开头先对参数进行校验,不符合要求直接返回或抛出错误:
async function saveActionToDB(action, user) { // 参数合法性校验 if (!action || typeof action !== 'string') { logger.error('无效的action参数', { receivedAction: action }); return Promise.reject(new Error('action必须是非空字符串')); } if (!user || typeof user !== 'string') { logger.error('无效的user参数', { receivedUser: user }); return Promise.reject(new Error('user必须是非空字符串')); } if (config.Logging.DB.type !== 'mssql' || !dbPool) return; // 后续插入逻辑... }
5. 事务需求(如果插入是业务流程的一部分)
如果这个插入操作需要和其他数据库操作(比如更新用户操作次数)保持原子性,就得用事务来保证:要么全部操作成功,要么全部回滚,避免数据不一致。
解决方案:使用MSSQL事务
示例代码:
async function saveActionWithTransaction(action, user) { if (config.Logging.DB.type !== 'mssql' || !dbPool) return; let transaction; try { transaction = new sql.Transaction(dbPool); await transaction.begin(); const request = new sql.Request(transaction); request.input('action', sql.VarChar, action); request.input('user', sql.VarChar, user); // 插入操作记录 await request.query(`INSERT INTO YourTableName (action, [user]) VALUES (@action, @user)`); // 可以添加其他事务内的操作,比如更新用户操作次数 // await request.query(`UPDATE Users SET action_count = action_count + 1 WHERE username = @user`); await transaction.commit(); logger.info('事务执行成功,操作记录已插入'); } catch (err) { if (transaction) await transaction.rollback(); logger.error('事务执行失败,已回滚所有操作', { error: err.message }); throw err; } }
内容的提问来源于stack exchange,提问作者Fede E.

