如何从SQL Server流式传输200万行而不会崩溃节点?

时间:2018-02-06 14:35:26

标签: sql-server node.js stream

我使用Node将200万行从SQL Server复制到另一个数据库,所以当然我使用“streaming”选项,如下所示:

    const sql = require('mssql')
    ...
    const request = new sql.Request()
    request.stream = true
    request.query('select * from verylargetable')     
    request.on('row', row => {
        promise = write_to_other_database(row);
    })

我的问题是我已经对每一行进行了异步操作(插入另一个数据库),这需要时间。

读取速度比写入速度快,因此“on row”事件不断发生,内存最终填满待处理的Promises,并最终导致Node崩溃。这令人沮丧 - “流媒体”的重点是避免这种情况,不是吗?

我该如何解决这个问题?

3 个答案:

答案 0 :(得分:1)

问题似乎是由于使用“行”事件读取流而导致的,这些事件不允许您控制流的流。这应该可以通过“管道”方法实现,但随后你最终会进入数据流并实现可写流 - 这可能很棘手。

一个简单的解决方案是使用Scramjet,这样您的代码就可以完成几行:

const sql = require('mssql')
const {DataStream} = require("scramjet");
//...

const request = new sql.Request()
request.stream = true
request.query('select * from verylargetable')     
request.pipe(new DataStream({maxParallel: 1}))  
                        // pipe to a new DataStream with no parallel processing
    .batch(64)
                        // optionally batch the requests that someone mentioned
    .consume(async (row) => write_to_other_database(row));
                        // flow control will be done automatically

Scramjet将使用promises来控制流量。您也可以尝试增加maxParallel方法,但请记住,在这种情况下,最后一行可以同时开始推送行。

答案 1 :(得分:0)

我自己的答案:我不是同时写入目标数据库,而是将每一行转换为“insert”语句,并将语句推送到消息队列(RabbitMQ,一个单独的进程)。这很快,并且可以跟上阅读速度。另一个节点进程从队列中拉出(更慢)并写入目标数据库。因此,行的大“后退日志”由消息队列本身处理,这很好。

答案 2 :(得分:0)

要间歇性地pause your request流式传输数百万行而不会崩溃。

sql.connect(config, err => {

    if (err) console.log(err);

    const request = new sql.Request();
    request.stream = true; // You can set streaming differently for each request
    request.query('select * from dbo.YourAmazingTable'); // or 
    request.execute(procedure)

    request.on('recordset', columns => {
        // Emitted once for each recordset in a query
        //console.log(columns);
    });

    let rowsToProcess = [];

    request.on('row', row => {
        // Emitted for each row in a recordset
        rowsToProcess.push(row);
        if (rowsToProcess.length >= 3) {
            request.pause();
            processRows();
          }
          console.log(row);
     });

    request.on('error', err => {
        // May be emitted multiple times
        console.log(err);
    });

    request.on('done', result => {
        // Always emitted as the last one
        processRows();
        //console.log(result);

    });

    const processRows = () => {
        // process rows
        rowsToProcess = [];
        request.resume();
      }