MongoDB: أدخل إذا لم يكن موجودًا
-
25-09-2019 - |
سؤال
كل يوم ، أتلقى مخزونًا من المستندات (تحديث). ما أريد القيام به هو إدراج كل عنصر غير موجود بالفعل.
- أريد أيضًا تتبع المرة الأولى التي أدخلتها فيها ، وآخر مرة رأيتها في تحديث.
- لا أريد أن يكون لدي مستندات مكررة.
- لا أرغب في إزالة مستند تم حفظه مسبقًا ، لكن ليس في التحديث الخاص بي.
- 95 ٪ (المقدرة) من السجلات غير معدلة من يوم لآخر.
أنا أستخدم برنامج تشغيل Python (Pymongo).
ما أقوم به حاليًا هو (الكود الزائف):
for each document in update:
existing_document = collection.find_one(document)
if not existing_document:
document['insertion_date'] = now
else:
document = existing_document
document['last_update_date'] = now
my_collection.save(document)
مشكلتي هي أنها بطيئة للغاية (40 دقيقة لأقل من 100000 سجل ، ولدي ملايين منهم في التحديث). أنا متأكد تمامًا من وجود شيء مبني للقيام بذلك ، لكن المستند الخاص بـ Update () هو MMMHHH ....http://www.mongodb.org/display/docs/updating )
هل يمكن لأحد أن ينصح كيفية القيام بذلك بشكل أسرع؟
المحلول
يبدو أنك تريد القيام "بالتراجع". MongoDB لديه دعم مدمج لهذا. تمرير معلمة إضافية إلى تحديثك () استدعاء: {Upsert: True}. علي سبيل المثال:
key = {'key':'value'}
data = {'key2':'value2', 'key3':'value3'};
coll.update(key, data, upsert=True); #In python upsert must be passed as a keyword argument
هذا يحل محل كتلة IF-Find-Else-update بالكامل. سيتم إدراجه إذا لم يكن المفتاح موجودًا وسيتم تحديثه إذا حدث ذلك.
قبل:
{"key":"value", "key2":"Ohai."}
بعد:
{"key":"value", "key2":"value2", "key3":"value3"}
يمكنك أيضًا تحديد البيانات التي تريد كتابتها:
data = {"$set":{"key2":"value2"}}
الآن سيقوم المستند الذي اخترته بتحديث قيمة "key2" فقط وترك كل شيء آخر دون مساس.
نصائح أخرى
اعتبارًا من MongoDB 2.4 ، يمكنك استخدام $ setoninsert (http://docs.mongodb.org/manual/reference/operator/setoninsert/)
قم بتعيين "insertion_date" باستخدام $ setoninsert و "last_update_date" باستخدام تعيين $ في أمر upsert الخاص بك.
لتحويل الرمز الكاذب إلى مثال عمل:
now = datetime.utcnow()
for document in update:
collection.update_one(
{"_id": document["_id"]},
{
"$setOnInsert": {"insertion_date": now},
"$set": {"last_update_date": now},
},
upsert=True,
)
يمكنك دائمًا جعل فهرس فريد ، مما يؤدي إلى رفض MongoDB حفظ المتضاربة. النظر في ما يلي باستخدام قذيفة mongodb:
> db.getCollection("test").insert ({a:1, b:2, c:3})
> db.getCollection("test").find()
{ "_id" : ObjectId("50c8e35adde18a44f284e7ac"), "a" : 1, "b" : 2, "c" : 3 }
> db.getCollection("test").ensureIndex ({"a" : 1}, {unique: true})
> db.getCollection("test").insert({a:2, b:12, c:13}) # This works
> db.getCollection("test").insert({a:1, b:12, c:13}) # This fails
E11000 duplicate key error index: foo.test.$a_1 dup key: { : 1.0 }
يمكنك استخدام Upsert مع مشغل $ setoninsert.
db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
1. استخدم التحديث.
بالاعتماد على إجابة Van Nguyen أعلاه ، استخدم التحديث بدلاً من الحفظ. يمنحك هذا الوصول إلى خيار Upsert.
ملاحظة: هذه الطريقة تتجاوز المستند بأكمله عند العثور عليه (من المستندات)
var conditions = { name: 'borne' } , update = { $inc: { visits: 1 }} , options = { multi: true };
Model.update(conditions, update, options, callback);
function callback (err, numAffected) { // numAffected is the number of updated documents })
1.A. استخدم مجموعة $
إذا كنت ترغب في تحديث مجموعة مختارة من المستند ، ولكن ليس كل شيء ، يمكنك استخدام طريقة SET $ مع التحديث. (تكرارا، من المستندات) ... لذا ، إذا كنت تريد ضبط ...
var query = { name: 'borne' }; Model.update(query, ***{ name: 'jason borne' }***, options, callback)
أرسلها كما ...
Model.update(query, ***{ $set: { name: 'jason borne' }}***, options, callback)
هذا يساعد على منع الكتابة فوق كل المستندات (المستندات) عن طريق الخطأ { name: 'jason borne' }
.
لا أعتقد أن MongoDB يدعم هذا النوع من الترشيح الانتقائي. لدي نفس المشكلة مثل Lemiz ، واستخدام تحديث (المعايير ، NewObj ، Upsert ، Multi) لا يعمل بشكل صحيح عند التعامل مع الطابع الزمني "الذي تم إنشاؤه" و "محدثة". بالنظر إلى البيان التالي:
update( { "name": "abc" },
{ $set: { "created": "2010-07-14 11:11:11",
"updated": "2010-07-14 11:11:11" }},
true, true )
السيناريو #1-مستند مع "اسم" من "ABC" غير موجود: يتم إنشاء مستند جديد باستخدام "name" = "ABC" ، "تم إنشاؤه" = 2010-07-14 11:11:11 ، و "التحديث" = 2010-07-14 11:11:11.
السيناريو رقم 2-مستند مع "اسم" من "ABC" موجود بالفعل مع ما يلي: "الاسم" = "ABC" ، "تم إنشاؤه" = 2010-07-12 09:09:09 ، و "التحديث" = 2010-07 -13 10:10:10. بعد الانتعاش ، سيكون المستند الآن هو نفس النتيجة في السيناريو رقم 1. لا توجد وسيلة لتحديدها في مرحلة التصميم التي يتم تعيين الحقول إذا تم إدخالها ، وأي الحقول تترك بمفردها إذا تم تحديثها.
كان الحل الخاص بي هو إنشاء فهرس فريد على Critera الحقول ، وإجراء إدراج ، وبعد ذلك مباشرة قم بإجراء تحديث فقط على حقل "التحديث".
ملخص
- لديك مجموعة موجودة من السجلات.
- لديك سجلات محددة تحتوي على تحديثات للسجلات الموجودة.
- بعض التحديثات لا تحديث أي شيء حقًا ، فهي تكرر ما لديك بالفعل.
- تحتوي جميع التحديثات على نفس الحقول الموجودة بالفعل ، وربما فقط قيم مختلفة.
- تريد تتبع عندما تم تغيير سجل آخر ، حيث تغيرت قيمة بالفعل.
لاحظ ، أنا أفترض أن Pymongo ، تغيير لتناسب لغتك المفضلة.
تعليمات:
قم بإنشاء المجموعة باستخدام فهرس مع فريد = صحيح حتى لا تحصل على سجلات مكررة.
تكرار على سجلات الإدخال الخاصة بك ، وإنشاء دفعات منها من 15000 سجل أو نحو ذلك. لكل سجل في الدُفعة ، قم بإنشاء قولان يتكون من البيانات التي تريد إدراجها ، على افتراض أن كل منها سيكون سجلًا جديدًا. أضف الطابع الزمني "الذي تم إنشاؤه" و "محدثة" إلى هذه. قم بإصدار هذا كأمر إدراج دفعة مع علامة "ContereNerror" = صحيح ، وبالتالي فإن إدراج كل شيء آخر يحدث حتى لو كان هناك مفتاح مكرر هناك (يبدو أنه سيكون هناك). سيحدث هذا بسرعة كبيرة. سائبة إدراج الصخور ، لقد حصلت على مستويات الأداء 15K/ثانية. المزيد من الملاحظات على Conteronerror ، انظر http://docs.mongodb.org/manual/core/write-operations/
تحدث إدراج السجل بسرعة كبيرة ، لذلك ستتم القيام به مع تلك الإدراج في أي وقت من الأوقات. الآن ، حان الوقت لتحديث السجلات ذات الصلة. افعل هذا مع استرجاع الدُفعة ، أسرع بكثير من واحد في وقت واحد.
تكرار على جميع سجلات الإدخال الخاصة بك مرة أخرى ، وإنشاء دفعات من 15 كيلو أو نحو ذلك. استخراج المفاتيح (الأفضل إذا كان هناك مفتاح واحد ، ولكن لا يمكن مساعدته إذا لم يكن هناك). استرجاع هذه المجموعة من السجلات من Mongo باستخدام استعلام db.collectionNameblah.find ({field: {$ in: [1 ، 2،3 ...}). لكل من هذه السجلات ، حدد ما إذا كان هناك تحديث ، وإذا كان الأمر كذلك ، قم بإصدار التحديث ، بما في ذلك تحديث الطابع الزمني "المحدث".
لسوء الحظ ، يجب أن نلاحظ أن MongoDB 2.4 وما يلي لا تتضمن عملية تحديث بالجملة. إنهم يعملون على ذلك.
نقاط التحسين الرئيسية:
- ستعمل الإدراج على تسريع عملياتك بكميات كبيرة.
- إن استرداد السجلات بشكل جماعي سيؤدي إلى تسريع الأمور أيضًا.
- التحديثات الفردية هي المسار الوحيد الممكن الآن ، لكن 10Gen تعمل عليه. من المفترض أن يكون هذا في 2.6 ، على الرغم من أنني لست متأكدًا مما إذا كان سيتم الانتهاء من ذلك بحلول ذلك الوقت ، فهناك الكثير من الأشياء التي يجب القيام بها (كنت أتابع نظام JIRA الخاص بهم).
بشكل عام ، يكون استخدام التحديث أفضل في MongoDB لأنه سيقوم فقط بإنشاء المستند إذا لم يكن موجودًا بعد ، على الرغم من أنني لست متأكدًا من كيفية العمل مع محول Python الخاص بك.
ثانياً ، إذا كنت بحاجة فقط إلى معرفة ما إذا كان هذا المستند موجودًا أم لا ، فإن عد () الذي يعيد سوى رقم سيكون خيارًا أفضل من Find_one الذي من المفترض أن ينقل المستند بالكامل من MongoDB الذي يسبب حركة مرور غير ضرورية.