You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Tedious与Node.js向SQL Server批量保存数组的异步循环问题

解决SQL Server批量插入时的RequestError(LoggedInSendingInitialSql状态异常)

问题根源

你遇到的错误是因为Tedious连接尚未完全进入LoggedIn就绪状态时就发起了SQL请求。当前代码在循环中批量创建Connection实例,每个连接的connect是异步操作——当connect事件触发时,连接可能还在执行初始化SQL的过程中(处于LoggedInSendingInitialSql状态),此时调用execSql就会触发状态异常。此外,每个请求新建连接的方式不仅低效,还容易引发连接资源竞争问题。

推荐解决方案:使用连接池管理连接

Tedious支持连接池机制,既能避免重复创建连接的开销,又能确保连接处于就绪状态再执行请求,同时配合参数化查询杜绝SQL注入风险。

步骤1:安装并初始化连接池

先安装连接池依赖:

npm install tedious-connection-pool

然后初始化连接池:

const ConnectionPool = require('tedious-connection-pool');
const { Request, TYPES } = require('tedious');
const { v4: uuidv4 } = require('uuid');

// 你的数据库配置
const dbConfig = {
  server: '你的SQL Server地址',
  authentication: {
    type: 'default',
    options: {
      userName: '账号',
      password: '密码'
    }
  },
  options: {
    database: '目标数据库',
    encrypt: true // 根据你的SQL Server配置调整
  }
};

// 连接池配置
const poolConfig = {
  min: 2, // 最小空闲连接数
  max: 10, // 最大连接数
  log: false // 关闭日志,调试时可开启
};

const pool = new ConnectionPool(poolConfig, dbConfig);

步骤2:改造插入函数(使用参数化查询)

将字符串拼接的SQL改为参数化查询,同时正确封装Promise:

function addNewRoom(pool, floorID, newroomname) {
  return new Promise((resolve, reject) => {
    // 从连接池获取连接
    pool.acquire((err, connection) => {
      if (err) {
        console.error('获取连接失败:', err.message);
        return reject(err);
      }

      const roomID = uuidv4();
      // 参数化SQL语句
      const request = new Request(
        `INSERT INTO [dbo].[room] (room_id, floor_id, room_name, time_created, time_modified)
         VALUES (@roomID, @floorID, @roomName, GETDATE(), GETDATE());`,
        (err, rowCount) => {
          connection.release(); // 执行完成后释放连接回池
          if (err) {
            console.error('插入失败:', err.message);
            return reject(err);
          }
          console.log(`${rowCount}条记录插入成功`);
          resolve(rowCount);
        }
      );

      // 添加参数(根据字段类型选择对应的TYPES)
      request.addParameter('roomID', TYPES.UniqueIdentifier, roomID);
      request.addParameter('floorID', TYPES.UniqueIdentifier, floorID);
      request.addParameter('roomName', TYPES.NVarChar, newroomname);

      connection.execSql(request);
    });
  });
}

步骤3:批量插入的循环控制

使用async/await控制插入流程,可选择串行或并行执行:

串行执行(适合数据量小,避免数据库压力)

async function batchInsertRooms(floorID, roomNameList) {
  for (const roomName of roomNameList) {
    try {
      await addNewRoom(pool, floorID, roomName);
    } catch (err) {
      console.error(`插入房间${roomName}失败:`, err.message);
      // 可选:若需中断流程,取消注释下面的代码
      // throw err;
    }
  }
  // 所有插入完成后关闭连接池(若后续不再使用)
  pool.drain();
}

// 调用执行
batchInsertRooms(floorID, newroomname);

并行执行(适合数据量大,控制并发数)

async function batchInsertRoomsParallel(floorID, roomNameList) {
  // 生成所有插入Promise
  const insertPromises = roomNameList.map(roomName => 
    addNewRoom(pool, floorID, roomName)
      .catch(err => console.error(`插入房间${roomName}失败:`, err.message))
  );
  // 等待所有Promise完成
  await Promise.all(insertPromises);
  pool.drain();
}

// 调用执行
batchInsertRoomsParallel(floorID, newroomname);

备选方案:不使用连接池时的修复

若不想用连接池,需确保连接完全进入LoggedIn状态后再执行SQL,同时完善Promise封装:

// 循环中修改连接监听逻辑
for (let i = 0; i < newroomname.length; i++) {
  const addroomconnection = new Connection(config);
  addroomconnection.on('connect', function (err) {
    if (err) {
      console.error('连接失败:', err.message);
      return;
    }
    // 等待连接进入就绪状态
    waitForConnectionReady(addroomconnection)
      .then(() => addNewRoom(addroomconnection, floorID, newroomname[i]))
      .then(() => addroomconnection.close())
      .catch(err => console.error('处理失败:', err.message));
  });
  addroomconnection.connect();
}

// 辅助函数:等待连接进入LoggedIn状态
function waitForConnectionReady(connection) {
  return new Promise((resolve) => {
    if (connection.state === 'LoggedIn') {
      return resolve();
    }
    const checkState = () => {
      if (connection.state === 'LoggedIn') {
        connection.removeListener('stateChange', checkState);
        resolve();
      }
    };
    connection.on('stateChange', checkState);
  });
}

// 完善addNewRoom的Promise封装
function addNewRoom(addroomconnection, floorID, newroomname) {
  return new Promise((resolve, reject) => {
    console.log("开始插入房间记录...");
    const roomID = uuidv4();
    const request = new Request(
      `INSERT INTO [dbo].[room] (room_id, floor_id, room_name, time_created, time_modified)
       VALUES ('${roomID}', '${floorID}', '${newroomname}', GETDATE(), GETDATE());`,
      (err, rowCount) => {
        if (err) {
          console.error(err.message);
          reject(err);
        } else {
          console.log(`${rowCount}条记录插入成功`);
          resolve(rowCount);
        }
      }
    );
    addroomconnection.execSql(request);
  });
}

关键注意事项

  • 杜绝SQL注入:永远不要用字符串拼接生成SQL语句,必须使用参数化查询。
  • 连接复用:重复创建连接是低效且易出问题的,优先使用连接池管理连接资源。
  • Promise正确封装:确保异步操作的Promise能正确resolve和reject,避免出现永久pending的状态。

内容的提问来源于stack exchange,提问作者SadCoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 10:05:22