mongodb: ใส่ถ้าไม่มี


146

ทุกวันฉันได้รับเอกสาร (อัปเดต) สิ่งที่ฉันต้องการทำคือแทรกแต่ละรายการที่ไม่มีอยู่

  • ฉันต้องการติดตามครั้งแรกที่ฉันแทรกพวกเขาและครั้งสุดท้ายที่ฉันเห็นพวกเขาในการอัปเดต
  • ฉันไม่ต้องการมีเอกสารซ้ำ
  • ฉันไม่ต้องการลบเอกสารที่ถูกบันทึกไว้ก่อนหน้านี้ แต่ไม่ได้อยู่ในการอัปเดตของฉัน
  • 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 นาทีสำหรับน้อยกว่า 100,000 บันทึกและฉันมีนับล้านในการอัปเดต) ฉันค่อนข้างมั่นใจว่ามีบางอย่างในตัวสำหรับการทำเช่นนี้ แต่เอกสารสำหรับการอัปเดต () คือ mmmhhh .... สรุปสั้น ๆ .... ( http://www.mongodb.org/display/DOCS/Updating )

ใครสามารถแนะนำวิธีการให้เร็วขึ้น?

คำตอบ:


153

ดูเหมือนว่าคุณต้องการที่จะ "สุดยอด" 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" เท่านั้นและปล่อยให้ทุกอย่างอื่นไม่ถูกแตะต้อง


5
นี่คือสิ่งที่ฉันต้องการ! ฉันจะไม่สัมผัสฟิลด์ insertion_date ได้อย่างไรถ้าวัตถุนั้นมีอยู่แล้ว?
LeMiz

24
คุณช่วยยกตัวอย่างการตั้งค่าฟิลด์ในการแทรกครั้งแรกและไม่อัพเดทถ้ามีอยู่ได้ไหม @VanNguyen
Ali Shakiba

7
ฉันคิดว่าส่วนแรกของคำตอบของคุณผิด coll.update จะแทนที่ข้อมูลเว้นแต่ว่าคุณจะใช้ $ set ดังนั้นหลังจากจริงแล้วจะเป็น: {'key2': 'value2', 'key3': 'value3'}
James Blackburn

9
-1 คำตอบนี้อันตราย คุณค้นหาด้วยค่าของ "คีย์" และจากนั้นคุณลบ "คีย์" ดังนั้นคุณจะไม่สามารถค้นหาได้ในภายหลัง นี่เป็นกรณีการใช้งานที่ไม่น่าเป็นไปได้
Mark E. Haase

23
คุณควรใช้ตัวดำเนินการ $ setOnInsert! Upsert จะอัปเดตเอกสารหากพบข้อความค้นหา
YulCheney

65

ในฐานะของ MongoDB 2.4 คุณสามารถใช้ $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )

ตั้ง 'insertion_date' โดยใช้ $ setOnInsert และ 'last_update_date' โดยใช้ $ set ในคำสั่ง upsert ของคุณ

หากต้องการเปลี่ยน pseudocode ของคุณให้เป็นตัวอย่างการทำงาน:

now = datetime.utcnow()
for document in update:
    collection.update_one(
        {"_id": document["_id"]},
        {
            "$setOnInsert": {"insertion_date": now},
            "$set": {"last_update_date": now},
        },
        upsert=True,
    )

3
สิ่งนี้ถูกต้องคุณสามารถตรวจสอบเอกสารที่ตรงกับตัวกรองและแทรกบางอย่างหากไม่พบโดยใช้ $ setOnInsert โปรดทราบว่ามีข้อผิดพลาดที่คุณไม่สามารถ $ setOnInsert กับฟิลด์ _id - มันจะพูดบางอย่างเช่น "ไม่สามารถปรับเปลี่ยนฟิลด์ _id" นี่เป็นข้อผิดพลาดแก้ไขใน v2.5.4 หรือมีประมาณ หากคุณเห็นข้อความหรือปัญหานี้ให้รับเวอร์ชั่นล่าสุด
Kieren Johnstone

19

คุณสามารถสร้างดัชนีที่ไม่ซ้ำกันได้เสมอซึ่งทำให้ MongoDB ปฏิเสธการบันทึกที่ขัดแย้งกัน พิจารณาสิ่งต่อไปนี้โดยใช้ mongodb shell:

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

12

คุณสามารถใช้ Upsert พร้อมกับตัวดำเนินการ $ setOnInsert

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})


11
สำหรับใครก็ตามที่สอบถามด้วย pymongo พารามิเตอร์ที่สามควรจะเป็นจริงหรือเพิ่มขึ้น = จริงและไม่ใช่ dict
S ..

6

1. ใช้อัปเดต

ภาพวาดจากคำตอบของ Van Nguyen ด้านบนให้ใช้การอัปเดตแทนการบันทึก สิ่งนี้ช่วยให้คุณเข้าถึงตัวเลือกเพิ่มได้

หมายเหตุ : วิธีการนี้จะแทนที่เอกสารทั้งหมดเมื่อพบ ( จากเอกสาร )

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)

นี้จะช่วยป้องกันการเขียนทับตั้งใจทั้งหมดของเอกสารของคุณ (s) { name: 'jason borne' }ด้วย


6

สรุป

  • คุณมีการรวบรวมระเบียนที่มีอยู่
  • คุณมีชุดระเบียนที่มีการปรับปรุงระเบียนที่มีอยู่
  • การอัปเดตบางรายการไม่ได้อัปเดตอะไรเลยจริง ๆ แต่เป็นการอัปเดตสิ่งที่คุณมีอยู่แล้ว
  • การอัปเดตทั้งหมดมีฟิลด์เดียวกันที่มีอยู่แล้วอาจมีค่าแตกต่างกัน
  • คุณต้องการติดตามเมื่อมีการเปลี่ยนแปลงเรคคอร์ดครั้งล่าสุดโดยที่ค่าเปลี่ยนแปลงจริง

หมายเหตุฉันสมมุติว่า PyMongo เปลี่ยนให้เหมาะกับภาษาที่คุณเลือก

คำแนะนำ:

  1. สร้างคอลเลกชันด้วยดัชนีที่มี unique = true เพื่อให้คุณไม่ได้รับระเบียนที่ซ้ำกัน

  2. วนซ้ำบันทึกการป้อนข้อมูลของคุณสร้างชุดของพวกเขาจาก 15,000 บันทึกหรือมากกว่านั้น สำหรับแต่ละระเบียนในแบทช์ให้สร้าง dict ที่ประกอบด้วยข้อมูลที่คุณต้องการแทรกโดยสมมติว่าแต่ละระเบียนจะเป็นระเบียนใหม่ เพิ่มการประทับเวลา 'สร้าง' และ 'อัปเดต' ให้กับสิ่งเหล่านี้ ปัญหานี้เป็นชุดคำสั่งแทรกด้วยการตั้งค่าสถานะ 'ContinueOnError' = true ดังนั้นการแทรกของทุกอย่างอื่นเกิดขึ้นแม้ว่าจะมีคีย์ซ้ำกันอยู่ในนั้น (ซึ่งดูเหมือนจะมี) สิ่งนี้จะเกิดขึ้นเร็วมาก ก้อนหินขนาดใหญ่แทรกฉันได้รับ 15k / วินาทีในระดับประสิทธิภาพ หมายเหตุเพิ่มเติมเกี่ยวกับ ContinueOnError ดูhttp://docs.mongodb.org/manual/core/write-operations/

    เม็ดมีดเกิดขึ้นเร็วมากดังนั้นคุณจะสามารถแทรกเม็ดมีดเหล่านั้นได้ในเวลาไม่นาน ตอนนี้ได้เวลาปรับปรุงระเบียนที่เกี่ยวข้องแล้ว ทำสิ่งนี้ด้วยการดึงแบทช์เร็วกว่าครั้งละมาก

  3. วนซ้ำทุกเรคคอร์ดอินพุตของคุณอีกครั้งสร้างแบทช์ 15K หรือมากกว่านั้น แยกคีย์ออก (ดีที่สุดหากมีหนึ่งคีย์ แต่ไม่สามารถช่วยได้หากไม่มี) ดึงเรคคอร์ดจำนวนมากจาก Mongo ด้วยเคียวรี db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ... }) แบบสอบถาม สำหรับแต่ละระเบียนเหล่านี้ตรวจสอบว่ามีการปรับปรุงและถ้าเป็นเช่นนั้นออกการปรับปรุงรวมถึงการปรับปรุงการประทับเวลา 'ปรับปรุง'

    น่าเสียดายที่เราควรทราบว่า MongoDB 2.4 และด้านล่างไม่รวมการดำเนินการอัปเดตจำนวนมาก พวกเขากำลังทำสิ่งนั้นอยู่

คะแนนการเพิ่มประสิทธิภาพที่สำคัญ:

  • เม็ดมีดจะเพิ่มความเร็วในการทำงานของคุณเป็นอย่างมาก
  • การดึงข้อมูลมาสค์ไรเดอร์จะช่วยเร่งความเร็วให้ได้เช่นกัน
  • การอัปเดตส่วนบุคคลเป็นเพียงเส้นทางที่เป็นไปได้ในตอนนี้ แต่ 10Gen กำลังดำเนินการอยู่ สันนิษฐานว่าน่าจะเป็นแบบ 2.6 แม้ว่าฉันจะไม่แน่ใจว่ามันจะเสร็จสิ้นหรือยัง แต่ก็มีหลายสิ่งที่ต้องทำ (ฉันติดตามระบบ Jira)

5

ฉันไม่คิดว่า mongodb รองรับการเลือก upserting ประเภทนี้ ฉันมีปัญหาเช่นเดียวกับ LeMiz และการใช้การอัปเดต (เกณฑ์ newObj, upsert, multi)ไม่ทำงานเมื่อจัดการกับเวลาที่ 'สร้าง' และ 'อัปเดต' รับข้อความสั่ง upert ต่อไปนี้:

update( { "name": "abc" }, 
        { $set: { "created": "2010-07-14 11:11:11", 
                  "updated": "2010-07-14 11:11:11" }},
        true, true ) 

สถานการณ์สมมติ # 1 - ไม่มีเอกสารที่มี 'ชื่อ' ของ 'abc': เอกสารใหม่ถูกสร้างขึ้นด้วย 'name' = 'abc', 'created' = 2010-07-14 11:11:11 และ 'อัพเดต' = 2010-07-14 11:11:11

สถานการณ์ # 2 - เอกสารที่มี 'ชื่อ' ของ 'abc' มีอยู่แล้วโดยมีสิ่งต่อไปนี้: 'name' = 'abc', 'created' = 2010-07-12 09:09:09 และ 'อัพเดท' = 2010-07 -13 10:10:10 หลังจากที่เพิ่มมากขึ้นตอนนี้เอกสารจะเหมือนกับผลลัพธ์ในสถานการณ์ # 1 ไม่มีวิธีที่จะระบุในการเพิ่มแทรกฟิลด์ใดถ้าการแทรกและฟิลด์ใดถูกปล่อยให้อยู่คนเดียวถ้าการปรับปรุง

ทางออกของฉันคือการสร้างดัชนีที่ไม่ซ้ำกันในเขตข้อมูลcriteraทำการแทรกและทันทีหลังจากนั้นทำการปรับปรุงเพียงแค่ในเขตข้อมูล 'ปรับปรุง'


4

โดยทั่วไปการใช้การอัปเดตนั้นดีกว่าใน MongoDB เพราะมันจะสร้างเอกสารหากยังไม่มีอยู่ แต่ฉันไม่แน่ใจว่าจะใช้งานอะแดปเตอร์หลามของคุณอย่างไร

ประการที่สองหากคุณต้องการรู้ว่าเอกสารนั้นมีอยู่หรือไม่ให้นับ () ที่ส่งคืนเฉพาะตัวเลขเท่านั้นจะเป็นตัวเลือกที่ดีกว่า find_one ซึ่งควรถ่ายโอนเอกสารทั้งหมดจาก MongoDB ของคุณซึ่งก่อให้เกิดการรับส่งข้อมูลที่ไม่จำเป็น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.