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

通过 NodeMongoDB 本地驱动程序使用 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()来释放资源?还是需要打开新的数据库连接?

87851 次浏览

Here's a potential solution based on the counting approach (I haven't tested it and there's no error trapping, but it should convey the idea).

The basic strategy is: Acquire the count of how many records need to be updated, save each record asynchronously and a callback on success, which will decrement the count and close the DB if the count reaches 0 (when the last update finishes). By using {safe:true} we can ensure that each update is successful.

The mongo server will use one thread per connection, so it's good to either a) close unused connections, or b) pool/reuse them.

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);
}
});
})
});
});

I found that using counter may apply to simple scenario, but may be hard in complicated situations. Here is a solution that I come up by closing the database connection when database connection is idle:

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);
));

This can be a general solution for any database Connections. maxDbIdleTime can be set as the same value as db query timeout or longer.

This is not very elegant, but I can't think of a better way to do this. I use NodeJs to run a script that queries MongoDb and Mysql, and the script hangs there forever if the database connections are not closed properly.

I came up with a solution that involves a counter like this. It does not depend on a count() call nor does it wait for a time out. It will close the db after all the documents in each() are exhausted.

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;
};

So that each time you are going to make a call like db.each() or db.save() you would use these methods to ensure the db is ready while working and closed when done.

Example from 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. **
}
});
});

Now, this assumes that the second to last callback from each makes it through the mydb.open() before the last callback from each goes to mydb.close().... so, of course, let me know if this is an issue.

So: put a mydb.open(db) before a db call and put a mydb.close(db) at the return point of the callback or after the db call (depending on the call type).

Seems to me that this kind of counter should be maintained within the db object but this is my current workaround. Maybe we could create a new object that takes a db in the constructor and wrap the mongodb functions to handle the close better.

It's best to use a pooled connection and then call db.close() in cleanup function at the end of your application's life:

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

See http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html

A bit old thread, but anyway.

Based on the suggestion from @mpobrien above, I've found the async module to be incredibly helpful in this regard. Here's an example pattern that I've come to adopt:

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();
}
);

Here's a solution I came up with. It avoids using toArray and it's pretty short and sweet:

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();
});
});
});

Modern way of doing this without counters, libraries or any custom code:

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'));
});
});

Tested it on version 3.2.7 of mongodb driver but according to link might be valid since version 2.0

Here an extended example to the answer given by pkopac, since I had to figure out the rest of the details:

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);

Here is a link to the difference between SIGINT and SIGTERM. I had to add the process.exit(), otherwise my node web-server didn't exit cleanly when doing Ctrl + C on the running process in command line.