mongodb: chèn nếu không tồn tại


146

Mỗi ngày, tôi nhận được một kho tài liệu (một bản cập nhật). Những gì tôi muốn làm là chèn từng mục chưa tồn tại.

  • Tôi cũng muốn theo dõi lần đầu tiên tôi chèn chúng và lần cuối cùng tôi nhìn thấy chúng trong một bản cập nhật.
  • Tôi không muốn có tài liệu trùng lặp.
  • Tôi không muốn xóa tài liệu đã được lưu trước đó nhưng không có trong bản cập nhật của tôi.
  • 95% (ước tính) của các hồ sơ không được sửa đổi từ ngày này sang ngày khác.

Tôi đang sử dụng trình điều khiển Python (pymongo).

Những gì tôi hiện đang làm là (mã giả):

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)

Vấn đề của tôi là nó rất chậm (40 phút cho ít hơn 100 000 hồ sơ và tôi có hàng triệu trong số chúng trong bản cập nhật). Tôi khá chắc chắn rằng có một cái gì đó được xây dựng để làm điều này, nhưng tài liệu để cập nhật () là mmmhhh .... một chút ngắn gọn .... ( http://www.mongodb.org/display/DOCS/Updating )

Ai đó có thể tư vấn làm thế nào để làm nó nhanh hơn?

Câu trả lời:


153

Âm thanh như bạn muốn làm một "upert". MongoDB đã tích hợp hỗ trợ cho việc này. Truyền tham số bổ sung cho lệnh gọi cập nhật () của bạn: {upsert: true}. Ví dụ:

key = {'key':'value'}
data = {'key2':'value2', 'key3':'value3'};
coll.update(key, data, upsert=True); #In python upsert must be passed as a keyword argument

Điều này thay thế hoàn toàn khối if-find-other-update của bạn. Nó sẽ chèn nếu khóa không tồn tại và sẽ cập nhật nếu có.

Trước:

{"key":"value", "key2":"Ohai."}

Sau:

{"key":"value", "key2":"value2", "key3":"value3"}

Bạn cũng có thể chỉ định dữ liệu nào bạn muốn viết:

data = {"$set":{"key2":"value2"}}

Bây giờ tài liệu đã chọn của bạn sẽ chỉ cập nhật giá trị của "key2" và không để lại mọi thứ khác.


5
Đây gần như là những gì tôi muốn! Làm thế nào tôi không thể chạm vào trường ins insert_date nếu đối tượng đã có mặt?
LeMiz

24
bạn có thể vui lòng cho một ví dụ về việc chỉ thiết lập một trường trong lần chèn đầu tiên và không cập nhật nó nếu tồn tại không? @VanNguyen
Ali Shakiba

7
Phần đầu tiên của câu trả lời của bạn là sai, tôi nghĩ. coll.update sẽ thay thế dữ liệu trừ khi bạn sử dụng $ set. Vì vậy, After sẽ thực sự là: {'key2': 'value2', 'key3': 'value3'}
James Blackburn

9
-1 Câu trả lời này là nguy hiểm. Bạn tìm thấy bằng giá trị của "khóa" và sau đó bạn xóa "khóa", để sau đó bạn sẽ không thể tìm lại được. Đây là một trường hợp sử dụng rất khó xảy ra.
Đánh dấu E. Haase

23
Bạn nên sử dụng toán tử $ setOnInsert! Upsert thậm chí sẽ cập nhật tài liệu nếu tìm thấy truy vấn.
YulCheney

63

Kể từ MongoDB 2.4, bạn có thể sử dụng $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )

Đặt 'ins insert_date' bằng $ setOnInsert và 'last_update_date' bằng cách sử dụng $ set trong lệnh upsert của bạn.

Để biến mã giả của bạn thành một ví dụ hoạt động:

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
Điều này là chính xác, bạn có thể kiểm tra tài liệu khớp với bộ lọc và chèn một cái gì đó nếu không tìm thấy, bằng cách sử dụng $ setOnInsert. Lưu ý rằng có một lỗi mà bạn không thể $ setOnInsert với trường _id - nó sẽ có nội dung như "không thể Mod trường _id". Đây là một lỗi, đã được sửa trong phiên bản 2.5.4. Nếu bạn thấy thông báo hoặc vấn đề này, chỉ cần lấy phiên bản mới nhất.
Kieren Johnstone

19

Bạn luôn có thể tạo một chỉ mục duy nhất, điều này khiến MongoDB từ chối lưu trữ xung đột. Hãy xem xét những điều sau đây được thực hiện bằng cách sử dụng shell 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 }


6

1. Sử dụng Cập nhật.

Vẽ từ câu trả lời của Van Nguyen ở trên, sử dụng cập nhật thay vì lưu. Điều này cho phép bạn truy cập vào tùy chọn upsert.

LƯU Ý : Phương pháp này ghi đè toàn bộ tài liệu khi được tìm thấy ( Từ các tài liệu )

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. Sử dụng $ set

Nếu bạn muốn cập nhật một lựa chọn của tài liệu, nhưng không phải toàn bộ, bạn có thể sử dụng phương thức $ set với cập nhật. (một lần nữa, từ các tài liệu ) ... Vì vậy, nếu bạn muốn đặt ...

var query = { name: 'borne' };  Model.update(query, ***{ name: 'jason borne' }***, options, callback)

Gửi nó dưới dạng ...

Model.update(query, ***{ $set: { name: 'jason borne' }}***, options, callback)

Điều này giúp ngăn ngừa vô tình ghi đè tất cả (các) tài liệu của bạn với { name: 'jason borne' }.


6

Tóm lược

  • Bạn có một bộ sưu tập các hồ sơ hiện có.
  • Bạn có một bộ hồ sơ có chứa các bản cập nhật cho các bản ghi hiện có.
  • Một số cập nhật không thực sự cập nhật bất cứ điều gì, chúng trùng lặp với những gì bạn đã có.
  • Tất cả các bản cập nhật chứa các trường giống nhau đã có, chỉ có thể là các giá trị khác nhau.
  • Bạn muốn theo dõi khi bản ghi được thay đổi lần cuối, trong đó giá trị thực sự thay đổi.

Lưu ý, tôi đoán PyMongo, thay đổi cho phù hợp với ngôn ngữ bạn chọn.

Hướng dẫn:

  1. Tạo bộ sưu tập với một chỉ mục với unique = true để bạn không nhận được các bản ghi trùng lặp.

  2. Lặp lại các bản ghi đầu vào của bạn, tạo ra các lô trong số 15.000 bản ghi hoặc hơn. Đối với mỗi bản ghi trong lô, hãy tạo một lệnh bao gồm dữ liệu bạn muốn chèn, giả sử mỗi bản ghi sẽ là một bản ghi mới. Thêm dấu thời gian 'đã tạo' và 'đã cập nhật' vào các dấu thời gian này. Phát hành lệnh này dưới dạng lệnh chèn hàng loạt với cờ 'ContinOnError' = true, do đó việc chèn mọi thứ khác xảy ra ngay cả khi có một khóa trùng lặp trong đó (nghe có vẻ như sẽ có). ĐIỀU NÀY S H RẤT RẤT NHANH. Hàng loạt chèn đá, tôi đã nhận được mức hiệu suất 15k / giây. Ghi chú thêm về ContinOnError, xem http://docs.mongodb.org/manual/core/write-operations/

    Việc chèn bản ghi xảy ra RẤT nhanh, do đó bạn sẽ hoàn thành ngay những phần chèn đó. Bây giờ, đã đến lúc cập nhật các hồ sơ liên quan. Làm điều này với một truy xuất hàng loạt, nhanh hơn nhiều so với một lần.

  3. Lặp lại tất cả các bản ghi đầu vào của bạn một lần nữa, tạo các lô 15K hoặc hơn. Trích xuất các khóa (tốt nhất nếu có một khóa, nhưng không thể được giúp đỡ nếu không có). Truy xuất bó bản ghi này từ Mongo với truy vấn db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ...}). Đối với mỗi bản ghi này, hãy xác định xem có bản cập nhật hay không và nếu có, hãy phát hành bản cập nhật, bao gồm cả cập nhật dấu thời gian 'đã cập nhật.

    Thật không may, chúng ta nên lưu ý, MongoDB 2.4 trở xuống KHÔNG bao gồm thao tác cập nhật hàng loạt. Họ đang làm việc trên đó.

Điểm tối ưu hóa chính:

  • Các phần chèn sẽ tăng tốc độ hoạt động của bạn lên hàng loạt.
  • Lấy hồ sơ en masse cũng sẽ tăng tốc mọi thứ.
  • Cập nhật cá nhân là con đường duy nhất có thể bây giờ, nhưng 10Gen đang làm việc trên đó. Có lẽ, điều này sẽ là trong 2.6, mặc dù tôi không chắc liệu nó sẽ kết thúc vào lúc đó hay không, có rất nhiều việc phải làm (Tôi đã theo dõi hệ thống Jira của họ).

5

Tôi không nghĩ mongodb hỗ trợ kiểu nâng cấp chọn lọc này. Tôi có cùng một vấn đề với LeMiz và sử dụng cập nhật (tiêu chí, newObj, upsert, multi) không hoạt động đúng khi xử lý cả dấu thời gian 'được tạo' và 'cập nhật'. Đưa ra tuyên bố upsert sau:

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

Kịch bản # 1 - tài liệu có 'tên' của 'abc' không tồn tại: Tài liệu mới được tạo với 'name' = 'abc', 'created' = 2010-07-14 11:11:11 và 'update' = 2010/07/14 11:11:11.

Kịch bản # 2 - tài liệu có 'tên' của 'abc' đã tồn tại với các mục sau: 'name' = 'abc', 'created' = 2010-07-12 09:09:09 và 'update' = 2010-07 -13 10:10:10. Sau khi nâng cấp, tài liệu bây giờ sẽ giống như kết quả trong kịch bản # 1. Không có cách nào để chỉ định trong một upert mà trường nào được đặt nếu chèn và trường nào được để lại một mình nếu cập nhật.

Giải pháp của tôi là tạo một chỉ mục duy nhất trên các trường critera , thực hiện thao tác chèn và ngay sau đó thực hiện cập nhật ngay trên trường 'đã cập nhật'.


4

Nói chung, sử dụng bản cập nhật sẽ tốt hơn trong MongoDB vì nó sẽ chỉ tạo tài liệu nếu nó chưa tồn tại, mặc dù tôi không chắc cách làm việc với bộ điều hợp python của bạn.

Thứ hai, nếu bạn chỉ cần biết liệu tài liệu đó có tồn tại hay không, thì Count () chỉ trả về một số sẽ là một lựa chọn tốt hơn find_one được cho là chuyển toàn bộ tài liệu từ MongoDB của bạn gây ra lưu lượng không cần thiết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.