我有一个庞大的MongoDB集合(约50万份文档)。
结构是这样的:
{'_id': '.....',
'passid':'ag325gdtew',
'text': '.......',
'count': '.......',
'title': '......',
'body': '.......'
}
字段passid
在许多文档中都是相同的,我希望将它们以不同的方式组合到每个字段中。
我想:
passid
所以新文件将是这样的:
{'_id': '.....',
'passid':'ag325gdtew',
'text': '.......', (string)
'count': ['..','...','..'] (list)
}
目前,我正在使用Python,但文件很大,脚本会持续运行数小时。
我做了什么:
passid
passid
passid
)检索具有相同passid
正如我所说,这非常耗时。你知道更快的方法吗?
以下是代码:
passids= db.collection.aggregate({ "$group": {"_id": '$passid'}})
for i in passids['result']:
doc = {}
doc['passid'] = i['_id']
documents = db.collection.find({"passid": i['_id']})
doc['count'] = []
doc['text'] = ""
for d in documents:
doc['text'] = doc['text'] + " " + d['text']
doc['text'] = doc['text'] + " " + d['title']
doc['count'].append(d['count'])
db.collection.remove(d)
db.collection.save(doc)
答案 0 :(得分:0)
如果您试图避免应用程序和数据库之间的大量网络流量,最好的选择通常是尝试尽可能接近(以网络术语)运行代码到数据库以获得最佳速度。< / p>
如果无法做到这一点,并且实际上只应在“一次性”操作中使用,则可以使用db.eval()
警告 必须在考虑使用之前,请仔细阅读
db.eval()
的手册页。虽然完成工作的最快方法是需要考虑的主要缺点:
- 此将在整个执行期间获取数据库的写锁定。
- 除了获取写锁之外,由于JavaScript实现的单线程特性,使用JavaScript解释器的其他任务(例如“mapReduce”作业)也无法运行。
- 这将无法在分片群集上运行,如果您的主机使用身份验证,则用户帐户将需要除基本读取和写入之外的特殊权限才能执行任务。
醇>
一旦你考虑了上述所有内容,你就可以从头脑中走出来,承认方法存在并继续前进。
只要您可以处理不同集合的输出,就可以从mapReduce
开始,这样可以简化逻辑
您可以定义映射器:
var mapper = function() {
var passid = this.passid;
delete this["_id"];
delete this["body"];
emit( passid, this );
};
然后定义一个reducer:
var reducer = function(key,values) {
var reducedObject = {
"text": "",
"count": []
};
values.forEach(function(value) {
reducedObject.text = reducedObject.text + " " + value.text;
reducedObject.text = reducedObject.text + " " + value.title;
reducedObject.push( value.count );
});
return reducedObject;
};
然后你可以运行mapReduce操作:
db.collection.mapReduce(
mapper,
reducer,
{
"out": { "replace": "newcollection" }
}
)
使用mapReduce输出就是这样,你不希望在最终输出中这样做,所以你可以像这样改变它:
db.eval(function() {
db.newcollection.find().forEach(function(doc) {
var newDoc = {};
for ( var k in doc.values ) {
newDoc[k] = doc.values[k];
}
db.newcollection.update({ _id: doc._id }, newDoc );
});
})
这将把事物放入重新整形的集合中,你甚至可以考虑在数据库之间移动它以解决锁定问题。这仍然可能使您处于需要将其与原始集合交换的位置,但有办法实现这一点。
作为替代方案,您基本上可以切换到它并立即运行db.eval()
操作。所以这基本上将流程转换为相应的JavaScript:
db.eval(function() {
var lastid = "";
var counter = 0;
var text = "";
var count = [];
db.collection.find().forEach(function(doc) {
if ( (doc.passid != lastid) && (counter != 0) ) {
db.collection.update(
{ "_id": doc._id },
{
"passid": lastid,
"text": text,
"count": count
}
);
text = "";
count = [];
}
text = text + " " + doc.text;
text = text + " " doc.title;
count.push( doc.count );
counter++;
lastid = passid;
});
})
因此,批量更改文档绝不是一件好事,但有一些方法可以解决这个问题并将所有操作保留在服务器上。
答案 1 :(得分:0)
根据我的经验,使用mongo进行此类操作的大部分缓慢来自数据库的往返,因此尽可能少地调用它。如果您的文档足够小(就像您的示例所示),以便整个集合适合内存,您可以通过进行单个多次插入和多次删除来节省大量时间:
passids= db.collection.aggregate({ "$group": {"_id": '$passid'}})
new_docs = []
for i in passids['result']:
doc = {}
doc['passid'] = i['_id']
documents = db.collection.find({"passid": i['_id']})
doc['count'] = []
doc['text'] = ""
for d in documents:
doc['text'] = doc['text'] + " " + d['text']
doc['text'] = doc['text'] + " " + d['title']
doc['count'].append(d['count'])
new_docs.append(doc)
# Instead of removing all the documents one by one,
# dropping the collection is much faster
db.collection.drop()
db.collection.insert(new_docs)
为了安全起见,我将文档保存在新的集合中,并在检查完所有内容后才删除旧文档。