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)

내 문제는 매우 느리다는 것입니다 (100000 개 미만의 레코드의 경우 40 분이며 업데이트에 수백만 개가 있습니다). 나는 이것을하기 위해 내장 된 것이 있다고 확신하지만 update ()에 대한 문서는 mmmhhh .... 조금 간결합니다 .... ( http://www.mongodb.org/display/DOCS/Updating )

누군가가 더 빨리하는 방법을 조언 할 수 있습니까?

답변:


153

"upsert"를하고 싶은 것 같습니다. MongoDB는이를 지원합니다. update () 호출에 추가 매개 변수를 전달하십시오 : {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
알리 Shakiba

7
당신의 대답의 첫 부분이 틀렸다고 생각합니다. coll.update는 $ set를 사용하지 않으면 데이터 를 대체 합니다. { 'key2': 'value2', 'key3': 'value3'}
James Blackburn

9
-1이 답변은 위험합니다. "key"값으로 찾은 다음 "key"를 지우면 나중에 다시 찾을 수 없습니다. 이것은 매우 사용 사례가 아닙니다.
Mark E. Haase 2018

23
$ setOnInsert 연산자를 사용해야합니다! Upsert는 검색어를 찾으면 문서를 업데이트합니다.
YulCheney

64

MongoDB 2.4부터는 $ setOnInsert를 사용할 수 있습니다 ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )

upsert 명령에서 $ setOnInsert를 사용하여 'insertion_date'를 설정하고 $ set을 사용하여 'last_update_date'를 설정하십시오.

의사 코드를 실제 예제로 바꾸려면 :

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를 사용하여 필터와 일치하는 문서를 확인하고 찾을 수없는 경우 삽입 할 수 있습니다. _id 필드로 $ setOnInsert를 할 수없는 버그가 있습니다. "_id 필드를 수정할 수 없습니다"와 같은 메시지가 표시됩니다. 이것은 v2.5.4에서 수정 된 버그였습니다. 이 메시지 나 문제가 표시되면 최신 버전을 받으십시오.
Kieren Johnstone

19

항상 고유 인덱스를 만들 수 있으므로 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 }


6

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 사용

전체 문서가 아닌 문서 선택을 업데이트하려면 $ set 메소드를 update와 함께 사용할 수 있습니다. (다시, 문서에서 ) ... 그래서 설정하고 싶다면 ...

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

다음으로 보내기 ...

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

이렇게하면 실수로 모든 문서를로 덮어 쓰는 것을 방지 할 수 있습니다 { name: 'jason borne' }.


6

요약

  • 기존 레코드 모음이 있습니다.
  • 기존 레코드에 대한 업데이트가 포함 된 레코드 세트가 있습니다.
  • 일부 업데이트는 실제로 아무것도 업데이트하지 않고 이미 가지고있는 것을 복제합니다.
  • 모든 업데이트에는 이미 존재하는 동일한 필드가 있으며 다른 값일 수도 있습니다.
  • 값이 실제로 변경된 레코드가 마지막으로 변경된시기를 추적하려고합니다.

참고로, PyMongo를 사용하고 있습니다. 선택한 언어에 맞게 변경하십시오.

명령:

  1. 레코드가 중복되지 않도록 unique = true 인덱스를 사용하여 컬렉션을 만듭니다.

  2. 입력 레코드를 반복하여 15,000 레코드 정도의 배치를 작성하십시오. 배치의 각 레코드에 대해 삽입하려는 데이터로 구성된 dict를 작성하십시오. 각 레코드는 새 레코드라고 가정합니다. 여기에 '만들어진'및 '업데이트 된'타임 스탬프를 추가하십시오. 'ContinueOnError'flag = true로 일괄 삽입 명령으로 이것을 실행하십시오. 따라서 중복 키가있는 경우에도 다른 모든 항목이 삽입됩니다 (있는 것처럼 들립니다). 이것은 매우 빨리 일어날 것입니다. 벌크 인서트는 15k / 초의 성능 수준을 얻었습니다. ContinueOnError에 대한 추가 정보는 http://docs.mongodb.org/manual/core/write-operations/를 참조 하십시오.

    레코드 삽입은 매우 빠르게 이루어 지므로 해당 삽입을 즉시 완료 할 수 있습니다. 이제 관련 레코드를 업데이트 할 차례입니다. 한 번에 하나보다 훨씬 빠른 배치 검색으로이를 수행하십시오.

  3. 모든 입력 레코드를 다시 반복하여 15K 정도의 배치를 만듭니다. 키를 추출하십시오 (하나의 키가있는 경우 가장 좋지만없는 경우에는 도움이되지 않습니다). db.collectionNameBlah.find ({field : {$ in : [1, 2,3 ...}) 쿼리를 사용하여 Mongo에서이 레코드 무리를 검색하십시오. 이러한 각 레코드에 대해 업데이트가 있는지 확인하고, 업데이트 된 경우 '업데이트 된'타임 스탬프 업데이트를 포함하여 업데이트를 발행하십시오.

    불행히도 MongoDB 2.4 이하에는 대량 업데이트 작업이 포함되어 있지 않습니다. 그들은 그 일을하고 있습니다.

주요 최적화 포인트 :

  • 인서트는 작업 속도를 크게 향상시킵니다.
  • 대량으로 레코드를 검색하면 속도가 빨라집니다.
  • 개별 업데이트는 현재 유일하게 가능한 경로이지만 10Gen은 현재 작업 중입니다. 아마도 이것은 2.6에있을 것입니다. 그러나 그것이 끝날지 확신 할 수는 없지만 할 일이 많이 있습니다 (Jira 시스템을 따르고 있습니다).

5

mongodb이 이러한 유형의 선택적 업 세팅을 지원하지 않는다고 생각합니다. LeMiz와 동일한 문제 가 있으며 '만들기'및 '업데이트 된'타임 스탬프를 모두 처리 할 때 update (criteria, newObj, upsert, multi) 사용 이 제대로 작동하지 않습니다. 다음 upsert 문이 주어지면 :

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

시나리오 # 1- 'name'이 'abc'인 문서가 존재하지 않음 : 'name'= 'abc', 'created'= 2010-07-14 11:11:11 및 'updated'= 2010-07-14 11:11:11.

시나리오 # 2- 'name'이 'abc'인 문서는 'name'= 'abc', 'created'= 2010-07-12 09:09:09 및 'updated'= 2010-07과 함께 이미 존재합니다. -13 10:10:10. upsert 후 문서는 시나리오 # 1의 결과와 동일합니다. 삽입시 어떤 필드를 설정하고 업데이트 할 때 어떤 필드를 그대로 두어야하는지 upsert에 지정할 방법이 없습니다.

내 솔루션은 critera 필드 에 고유 인덱스를 만들고 삽입을 수행 한 다음 바로 'updated'필드에서 바로 업데이트를 수행하는 것이 었습니다 .


4

일반적으로 업데이트가 MongoDB에서 더 좋습니다. 아직 문서가 존재하지 않으면 문서를 작성하기 때문에 파이썬 어댑터에서 어떻게 작동하는지 잘 모르겠습니다.

두 번째로, 해당 문서가 존재하는지 여부 만 알 필요가있는 경우, 숫자 만 반환하는 count ()가 find_one보다 MongoDB에서 전체 문서를 전송하여 불필요한 트래픽을 발생시키는 것보다 더 나은 옵션이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.