何时关闭Nodejs中的MongoDB数据库连接

时间:2011-12-04 07:17:34

标签: javascript mongodb node.js asynchronous

通过Node MongoDB本机驱动程序使用Nodejs和MongoDB。需要检索一些文档并进行修改,然后将它们保存回来。这是一个例子:

db.open(function (err, db) {
  db.collection('foo', function (err, collection) {
    var cursor = collection.find({});
    cursor.each(function (err, doc) {
      if (doc != null) {
        doc.newkey = 'foo'; // Make some changes
        db.save(doc); // Update the document
      } else {
        db.close(); // Closing the connection
      }
    });
  });
});

具有异步性质,如果更新文档的过程需要更长时间,那么当光标到达文档末尾时,数据库连接将关闭。并非所有更新都保存到数据库中。

如果省略db.close(),则所有文档都会正确更新,但应用程序会挂起,永不退出。

我看到一篇帖子建议使用计数器跟踪更新次数,当回落到零时,然后关闭数据库。但我在这里做错了吗?处理这种情况的最佳方法是什么?是否必须使用db.close()来释放资源?或者是否需要打开新的数据库连接?

8 个答案:

答案 0 :(得分:24)

这是一个基于计数方法的潜在解决方案(我没有对它进行测试,也没有错误捕获,但它应该传达这个想法)。

基本策略是:获取需要更新的记录数,异步保存每条记录以及成功回调,如果计数达到0(上次更新完成时),将减少计数并关闭数据库)。通过使用{safe:true},我们可以确保每次更新都成功。

mongo服务器将为每个连接使用一个线程,因此最好是a)关闭未使用的连接,或者b)池/重用它们。

db.open(function (err, db) {
  db.collection('foo', function (err, collection) {
    var cursor = collection.find({});
    cursor.count(function(err,count)){
      var savesPending = count;

      if(count == 0){
        db.close();
        return;
      }

      var saveFinished = function(){
        savesPending--;
        if(savesPending == 0){
          db.close();
        }
      }

      cursor.each(function (err, doc) {
        if (doc != null) {
          doc.newkey = 'foo'; // Make some changes
          db.save(doc, {safe:true}, saveFinished);
        }
      });
    })
  });
});

答案 1 :(得分:13)

最好使用池连接,然后在应用程序生命周期结束时在清理函数中调用db.close():

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

请参阅http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html

有点旧线程,但无论如何。

答案 2 :(得分:4)

我发现使用计数器可能适用于简单的场景,但在复杂情况下可能很难。这是我在数据库连接空闲时关闭数据库连接的解决方案:

var dbQueryCounter = 0;
var maxDbIdleTime = 5000; //maximum db idle time

var closeIdleDb = function(connection){
  var previousCounter = 0;
  var checker = setInterval(function(){
    if (previousCounter == dbQueryCounter && dbQueryCounter != 0) {
        connection.close();
        clearInterval(closeIdleDb);
    } else {
        previousCounter = dbQueryCounter;
    }
  }, maxDbIdleTime);
};

MongoClient.connect("mongodb://127.0.0.1:27017/testdb", function(err, connection)(
  if (err) throw err;
  connection.collection("mycollection").find({'a':{'$gt':1}}).toArray(function(err, docs) {
    dbQueryCounter ++;
  });   
  //do any db query, and increase the dbQueryCounter
  closeIdleDb(connection);
));

这可以是任何数据库连接的通用解决方案。 maxDbIdleTime可以设置为与db query timeout相同或更长的值。

这不是很优雅,但我想不出更好的方法来做到这一点。我使用NodeJ来运行查询MongoDb和Mysql的脚本,如果数据库连接没有正确关闭,脚本将永远挂起。

答案 3 :(得分:1)

根据上面@mpobrien的建议,我发现async模块在​​这方面非常有帮助。以下是我采用的示例模式:

const assert = require('assert');
const async = require('async');
const MongoClient = require('mongodb').MongoClient;

var mongodb;

async.series(
    [
        // Establish Covalent Analytics MongoDB connection
        (callback) => {
            MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
                assert.equal(err, null);
                mongodb = db;
                callback(null);
            });
        },
        // Insert some documents
        (callback) => {
            mongodb.collection('sandbox').insertMany(
                [{a : 1}, {a : 2}, {a : 3}],
                (err) => {
                    assert.equal(err, null);
                    callback(null);
                }
            )
        },
        // Find some documents
        (callback) => {
            mongodb.collection('sandbox').find({}).toArray(function(err, docs) {
                assert.equal(err, null);
                console.dir(docs);
                callback(null);
            });
        }
    ],
    () => {
        mongodb.close();
    }
);

答案 4 :(得分:1)

这是我提出的解决方案。它避免使用toArray并且它非常简短和甜蜜:

var MongoClient = require('mongodb').MongoClient;

MongoClient.connect("mongodb://localhost:27017/mydb", function(err, db) {
  let myCollection = db.collection('myCollection');
  let query = {}; // fill in your query here
  let i = 0;
  myCollection.count(query, (err, count) => { 
    myCollection.find(query).forEach((doc) => {
      // do stuff here
      if (++i == count) db.close();
    });
  });
});

答案 5 :(得分:0)

我提出了一个涉及这样的计数器的解决方案。它不依赖于count()调用,也不等待超时。在每个()中的所有文档都用尽之后,它将关闭数据库。

var mydb = {}; // initialize the helper object.

mydb.cnt = {}; // init counter to permit multiple db objects.

mydb.open = function(db) // call open to inc the counter.
{
  if( !mydb.cnt[db.tag] ) mydb.cnt[db.tag] = 1;
  else mydb.cnt[db.tag]++;
}; 

mydb.close = function(db) // close the db when the cnt reaches 0.
{
  mydb.cnt[db.tag]--;
  if ( mydb.cnt[db.tag] <= 0 ) {
    delete mydb.cnt[db.tag];
    return db.close();
  }
  return null;
};

因此,每次要进行db.each()或db.save()等调用时,都可以使用这些方法确保数据库在工作时就绪,并在完成时关闭。

OP的例子:

foo = db.collection('foo');

mydb.open(db); // *** Add here to init the counter.**  
foo.find({},function(err,cursor)
{
  if( err ) throw err; 
  cursor.each(function (err, doc)
  {
    if( err ) throw err;
    if (doc != null) {
      doc.newkey = 'foo';
      mydb.open(db); // *** Add here to prevent from closing prematurely **
      foo.save(doc, function(err,count) {
        if( err ) throw err;
        mydb.close(db); // *** Add here to close when done. **
      }); 
    } else {
      mydb.close(db); // *** Close like this instead. **
    }
  });
});

现在,假设每个回调的第二个到最后一个回调使得它通过mydb.open(),然后从每个回调到mydb.close()....所以,当然,让我知道是否这是一个问题。

所以:在db调用之前放一个mydb.open(db)并在回调的返回点或db调用之后放置一个mydb.close(db)(取决于调用类型)。

我觉得这种计数器应该在db对象中维护,但这是我目前的解决方法。也许我们可以创建一个新的对象,在构造函数中使用db并包装mongodb函数以更好地处理close。

答案 6 :(得分:0)

无需计数器,库或任何自定义代码的现代方式:

let MongoClient = require('mongodb').MongoClient;
let url = 'mongodb://yourMongoDBUrl';
let database = 'dbName';
let collection = 'collectionName';

MongoClient.connect(url, { useNewUrlParser: true }, (mongoError, mongoClient) => {
   if (mongoError) throw mongoError;

   // query as an async stream
   let stream = mongoClient.db(database).collection(collection)
        .find({}) // your query goes here
        .stream({
          transform: (readElement) => {
            // here you can transform each element before processing it
            return readElement;
          }
        });

   // process each element of stream (async)
   stream.on('data', (streamElement) => {
        // here you process the data
        console.log('single element processed', streamElement);
   });

   // called only when stream has no pending elements to process
   stream.once('end', () => {
     mongoClient.close().then(r => console.log('db successfully closed'));
   });
});

在mongodb驱动程序3.2.7版上进行了测试,但根据链接,自version 2.0起可能有效

答案 7 :(得分:0)

这里是the answer given by pkopac的扩展示例,因为我不得不弄清楚其余的细节:

const client = new MongoClient(uri);
(async () => await client.connect())();

// use client to work with db
const find = async (dbName, collectionName) => {
  try {
    const collection = client.db(dbName).collection(collectionName);
    const result = await collection.find().toArray()
    return result;
  } catch (err) {
    console.error(err);
  }
}

const cleanup = (event) => { // SIGINT is sent for example when you Ctrl+C a running process from the command line.
  client.close(); // Close MongodDB Connection when Process ends
  process.exit(); // Exit with default success-code '0'.
}

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

这里是link to the difference between SIGINTSIGTERM。 我必须添加process.exit(),否则在命令行的运行进程中执行Ctrl + C时,我的节点Web服务器不会干净退出。