1. find 소개
- 몽고DB에서 find 함수는 쿼리에 사용
- 쿼리는 컬렉션에서 도큐먼트의 subset을 반환
- find의 첫 매개변수에 따라 어떤 도큐먼트를 가져올지 결정
- 빈 쿼리 도큐먼트({})는 컬렉션 내 모든 것과 일치하며 매개변수에 쿼리 도큐먼트가 없으면 find 함수는 빈 쿼리 도큐먼트 {}로 인식
- 쿼리 도큐먼트에 여러 key-value 쌍을 추가해 검색을 제한할 수 있음
- 대부분의 데이터형에서 간단히 작동하며 정수형은 정수형에, 불리언형은 불리언형에, 문자열형은 문자열형에 일치함
- 간단한 데이터형은 찾으려는 값만 지정하면 쉽게 쿼리 할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// "age"가 27인 모든 도큐먼트를 조회 | |
db.users.find({"age": 27}) | |
// "username" 키 값이 "joe"인 경우와 문자열형이 일치하는 도큐먼트 조회 | |
db.users.find({"username": "joe"}) | |
// 27살이면서 이름이 "joe"인 모든 사용자 조회 | |
db.users.find({"username": "joe", "age": 27}) |
1.1 반환 받을 키 지정
- find 또는 findOne의 두 번째 매개변수에 원하는 키를 지정하면 원하는 key-value 정보만 조회 가능
- 이는 네트워크상의 데이터 전송량과 클라이언트 측에서 도큐먼트를 디코딩하는 데 드는 시간과 메모리를 줄여줌
- i.g. 사용자 정보 컬렉션에서 "username"과 "email" 키의 값만 원할 때는 아래와 같이 쿼리
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
db.users.find({}, {"username": 1, "email": 1}) | |
{ | |
"_id": ObjectId("4ba0f0dfd22aa494fd523620"), | |
"username": "joe", | |
"email": "joe@example.com" | |
} |
- 또한 두 번째 매개변수를 사용해서 특정 key-value 쌍을 제외한 결과를 얻을 수도 있음
- i.g. 다양한 키가 있는 도큐먼트에서 "fatal_weakness" 키 값을 쓸 일이 전혀 없다면 아래와 같이 제외시킴
- _id 값 또한 반환 제외시킬 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
db.users.find({}, {"fatal_weakness": 0}) |
1.2 제약 사항
- 쿼리에는 몇 가지 제약이 존재함
- 데이터베이스에서 쿼리 도큐먼트 값은 반드시 상수여야 하는데 이는 도큐먼트 내 다른 키의 값을 참조할 수 없음을 의미
- $where 쿼리와 같은 방법이 있지만 일반 쿼리로 처리할 수 있게 도큐먼트 구조를 약간 재구성하면 더 나은 성능을 얻을 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 도큐먼트에 재고 수량 키 "in_stock"과 판매 수량 키 "num_sold"가 있다고 가정 | |
// 두 키는 상수이므로 참고할 수 없기 때문에 다음 쿼리는 동작 안 함 | |
db.stock.find({"in_stock": "this.num.sold"}); // 동작 X | |
// 누군가가 상품을 구매할 때마다 "in_stock" 값을 감소시킨다고 가정 | |
// 품절 상품 확인하는 쿼리는 다음과 같음 | |
db.stock.find({"in_stock": 0}) |
2. 쿼리 조건
- 쿼리는 완전 일치 외에도 범위, OR 절, 부정 조건 등 복잡한 조건으로 검색 가능
2.1 쿼리 조건절
- <, <=, >, >=에 해당하는 비교 연산자는 각각 "$lt", "$lte", "$gt", "$gte"
- 조합해 사용하면 특정 범위 내 값을 쿼리 가능
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 18세 ~ 30세 사이의 사용자 조회 | |
db.users.find({"age": {"$gte": 18, "$lte": 30}}) | |
// 2007년 1월 1일 이전에 등록한 사람 조회 | |
start = new Date("01/01/2007") | |
db.users.find({"registered": {"$lt": start}}) | |
// 사용자명이 "joe"가 아닌 사용자를 모두 조회 | |
db.users.find({"username": {"$ne": "joe"}}) |
2.2 OR 쿼리
- 몽고DB에서 OR 쿼리에는 두 가지 방법이 있음
- "$in"은 하나의 키를 다양한 값과 비교하는 쿼리에 사용
- "$or"은 더 일반적이며, 여러 키를 주어진 값과 비교하는 쿼리에 사용
- 하나의 키에 일치시킬 값이 여러 개 있다면 "$in"에 조건 배열을 사용
- "$in"은 매우 유연해 여러 개의 값을 쓸 수 있을 뿐 아니라 서로 다른 데이터형도 사용 가능
- "$in"의 조건 배열에 값이 하나만 주어지면 바로 일치하는 것을 찾음
- "$nin"은 "$in"과 반대로 배열 내 조건과 일치하지 않는 도큐먼트를 반환
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 추첨 당첨자를 뽑는 상황에서 당첨 번호가 725, 542, 390 | |
db.raffle.find({"ticket_no": {"$in": [725, 542, 390]}}) | |
// 추첨에서 당첨되지 않은 사람은 모두 반환 | |
db.raffle.find({"ticket_no": {"$nin": [725, 542, 390]}}) |
- "$or"은 가능한 조건들의 배열을 취함
- "$or"은 다른 조건절도 포함 가능
- 일반적인 AND 쿼리에서는 최소한의 인수로 최적의 결과를 추려내야 하는 반면 OR 쿼리는 반대로 첫 번째 인수가 일치하는 도큐먼트가 많을수록 효율적
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 앞선 추첨권 예제에 "$or" 사용 | |
db.raffle.find({"$or": [{"ticket_no": 725}, {"winner": true}]}) | |
// "ticket_no"가 세 번호 중 적어도 하나와 일치하거나 "winner"가 true인 경우를 조회 | |
db.raffle.find({"$or": [{"ticket_no": {"$in": [725, 542, 390]}}, {"winner": true}]}) |
- "$or" 연산자가 항상 작동하는 동안에는 가능한 한 "$in"을 사용하는 것을 권장
- 쿼리 옵티마이저는 "$in"을 더 효율적으로 다룸
2.3 $not
- "$not"은 메타 조건절이며 어떤 조건에도 적용할 수 있음
- "$not"은 정규 표현식과 함께 사용해 주어진 패턴과 일치하지 않는 도큐먼트를 조회할 때 특히 유용
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// "id_num"이 2, 3, 4, 5, 7, 8, 9, 10, 12 등인 사용자를 조회 | |
db.users.find({"id_num": {"$not": {"$mod": [5, 1]}}}) |
3. 형 특정 쿼리
- 몽고DB에서는 도큐먼트 내 다양한 데이터형을 사용할 수 있으며 일부 데이터형은 쿼리 시 형에 특정하게 작동함
3.1 null
- null은 스스로 일치하는 것을 찾음
- null은 `존재하지 않음`이라는 뜻이므로 키가 null인 값을 쿼리 하면 해당 키를 갖지 않는 도큐먼트도 반환함 i.g. 아래 사진에서 db.c.find({"z": null}) 참고
- 값이 null인 키만 찾고 싶다면 키가 null인 값을 쿼리하고, "$exists" 조건절을 사용해 null 존재 여부를 확인하면 됨

3.2 정규 표현식
- "$regex"는 쿼리에서 패턴 일치 문자열을 위한 정규식 기능 제공
- 정규 표현식은 문자열 일치를 유연하게 하는 데 유용
- i.g. 이름이 Joe나 joe인 사용자를 모두 조회하기 위해 정규 표현식을 이용해 대소문자 구별 없이 조회
db.users.find({"name": {"$regex": /joe/i}})
- 몽고DB는 정규 표현식 일치에 펄 호환 정규 표현식 (PCRE) 라이브러리를 사용하며, PCRE에 쓸 수 있는 모든 문법은 몽고DB에서도 사용 가능
- 쿼리 하기쿼리 하기 전에 먼저 자바스크립트 셸로 해당 정규 표현식이 의도한 대로 동작하는지 확인해보는 것을 권장
3.3 배열에 쿼리하기
- 배열 요소 쿼리는 스칼라 쿼리와 같은 방식으로 동작하도록 설계됨
- i.g. 아래처럼 쿼리를 이용해서 일치하는 도큐먼트를 성공적으로 찾을 수 있음

부연 설명
- 이때 배열을 {"fruit": "apple", "fruit": "banana", "fruit": "peach"}와 같은 도큐먼트로 가정
$all 연산자
- 두 개 이상의 배열 요소가 일치하는 배열을 찾으려면 "$all"을 사용하며 이는 배열 내 여러 요소와 일치하는지 확인하게 해 줌
- i.g. "apple"과 "banana" 요소를 "$all" 연산자와 함께 써서 해당 도큐먼트를 조회할 수 있음
- 순서는 중요하지 않으며 배열에 요소가 하나뿐일 때는 "$all" 연산자 사용 여부와 관계없이 결과가 같음

- 배열 내 특정 요소를 쿼리 하려면 key.index 구문을 이용해 순서를 지정
- 배열 인덱스는 0-index

$size 연산자
- "$size"는 특정 크기의 배열을 쿼리 하는 유용한 조건절
db.food.find({"fruit": {"$size": 3}})
- 자주 쓰이는 쿼리로, 크기의 범위로 쿼리 할 수 있음
- "$size"는 다른 $ 조건절과 결합해 사용할 수 없지만 도큐먼트에 "size" 키를 추가하면 여러 쿼리를 처리 가능
- 값의 증가는 매우 빠르게 이뤄지므로 성능은 크게 걱정할 필요 없고 도큐먼트를 저장하고 나면 아래와 같은 쿼리가 가능함
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
db.food.update(criteria, {"$push": {"fruit": "strawberry"}}) | |
db.food.update(criteria, {"$push": {"fruit": "strawberry"}, "$inc": {"size": 1}}) | |
db.food.find({"size": {"$gt": 3}}) |
$slice 연산자
- find의 두 번째 매개변수에는 반환받을 특정 키를 지정할 수 있음 (optional)
- "$slice" 연산자를 사용해서 배열 요소의 부분집합을 반환받을 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 블로그 게시물에서 먼저 달린 댓글 열 개를 반환받는다고 가정 | |
db.blog.posts.findOne(criteria, {"comments": {"$slice": 10}}) | |
// 나중에 달린 댓글 열 개를 반환받으려면 -10을 지정 | |
db.blog.posts.findOne(criteria, {"comments": {"$slice": -10}}) | |
// 오프셋과 요소 개수를 지정해 원하는 범위 내 결과를 반환할 수 있음 | |
// 아래 쿼리는 처음 23개를 건너뛰고, 24번째 요소부터 max(33번째 요소, 요소 전체 개수)까지 반환함 | |
db.blog.posts.findOne(criteria, {"comments": {"$slice": [23, 10]}}) |
- 명시하지 않는 키는 반환하지 않는 다른 키 명시자들과는 달리 "$slice" 연산자는 특별히 명시하지 않는 한 도큐먼트 내 모든 키를 반환

일치하는 배열 요소의 반환
- 배열 요소의 인덱스를 알고 있다면 "$slice" 연산자가 유용하게 쓰이지만, 때로는 특정 기준과 일치하는 배열 요소를 원할 수도 있음
- $ 연산자를 사용하면 일치하는 요소를 반환받을 수 있음
- i.g. 블로그 예제에서 Bob이 쓴 댓글을 얻으려면 아래와 같이 쿼리 작성

부연 설명
- 각 도큐먼트에서 첫 번째로 일치하는 댓글만 반환
- Bob이 해당 게시물에 댓글을 여러 개 남기면 "comments" 배열에 존재하는 첫 번째 댓글만 반환
배열 및 범위 쿼리의 상호작용
- 도큐먼트 내 스칼라 (비배열 요소)는 쿼리 기준의 각 절과 일치해야 함
- i.g. {"x": {"$gt": 10, "$lt": 20}}과 같이 쿼리 했다면 "x"는 10보다 크고 20보다 작아야 함
- 하지만 도큐먼트와 "x" 필드가 배열이라면 각 절의 조건을 충족하는 도큐먼트가 일치되며 각 쿼리 절을 서로 다른 배열 요소와 일치할 수 있음
- i.g. "x"의 값이 10과 20 사이인 도큐먼트를 모두 찾으려 할 때, 쿼리를 단순히 db.test.find({"x": {"$gt": 10, "$lt": 20}})과 같이 구성하면 {"x": 15}와 같이 전체 일치하는 도큐먼트뿐만 아니라 {"x": [5, 25]}와 같이 첫 번째 절만 일치하는 도큐먼트도 반환
- 정리하면 범위가 모든 다중 요소 배열과 일치하기 때문에 해당 방법을 사용하면 배열에 대한 범위 쿼리가 본질적으로 쓸모 없어짐
- 원하는 결과를 얻기 위해서는 다음 두 가지 방법 중 하나를 사용해야 함
- "$elemMatch" 연산자를 사용하면 몽고DB는 두 절을 하나의 배열요소와 비교하지만 "$elmMatch" 연산자는 비배열 요소를 일치시키지 않는다는 함정이 있음 ("$elemMatch"는 배열 요소에 대한 범위 쿼리에 유용)
- 쿼리 하는 필드에 인덱스가 있다면 min 함수와 max 함수를 사용해 "$gt"와 "$lt" 값 사이로 인덱스 범위를 제한해 쿼리 할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// {"x": 15}는 "x" 필드가 배열이 아니므로 쿼리와 일치하지 않음 | |
// "$elemMatch"는 배열 요소에 대한 범위 쿼리에 유용 | |
db.test.find({"x": {"$elemMatch": {"$gt": 10, "$lt": 20}}}) | |
> // 결과 없음 | |
// min, max를 사용하는 쿼리는 [5, 25]는 누락시키고 10과 20 사이의 인덱스만을 통과시킴 | |
db.test.find({"x": {"$gt": 10, "$lt": 20}}).min({"x": 10}).max({"x": 20}) | |
> {"x": 15} |
- 일반적으로 배열을 포함하는 도큐먼트에 범위 쿼리를 할 때 min 함수와 max 함수를 사용하면 좋음
- 배열에 대한 "$gt"/"$lt" 쿼리는 어떤 값이든 허용하므로 범위 내 값뿐만 아니라 모든 인덱스 항목을 검색하기 때문에 비효율적
3.4 내장 도큐먼트에 쿼리 하기
- 내장 도큐먼트 쿼리는 다음과 같이 두 가지 방식으로 나뉨
- 도큐먼트 전체를 대상으로 하는 방식
- 도큐먼트 내 key-value 쌍 각각을 대상으로 하는 방식
- 전체 도큐먼트를 대상으로 하는 쿼리는 일반적인 쿼리와 동일하게 작동하지만 서브 도큐먼트 전체에 쿼리 하려면 서브 도큐먼트와 정확히 일치해야 함 (순서 또한 일치해야 함)
- 내장 도큐먼트에 쿼리 할 때는 가능하다면 특정 키로 쿼리하는 방법이 좋음
- 도큐먼트 전체를 대상으로 정확히 일치시키는 방법이 아니므로 스키마가 변경되더라도 모든 쿼리가 정상적으로 작동함
- 내장 도큐먼트의 키를 쿼리할 때는 점 표기법을 사용
- i.g. 아래 쿼리는 name.middle 키가 추가되더라도 점 표기법을 사용했기 때문에 name.first와 name.last가 일치할 경우 도큐먼트를 반환함
db.people.find({"name.first": "Joe", "name.last": "Scmoe"})
- 쿼리 도큐먼트와 다른 도큐먼트의 타입의 큰 차이점은 점 표기법
- 쿼리 도큐먼트는 점을 포함할 수 있고, 이는 내장 도큐먼트 내 항목에 접근할 수 있다는 의미
- 또한 점 표기법은 입력하는 도큐먼트에. 문자를 사용할 수 없는 이유이기도 함
- 이러한 제약 조건은 URL을 키로 저장할 때 자주 문제를 일으키지만
- 도큐먼트를 입력하기 전이나 꺼낸 후에 . 문자를 URL에 쓰이지 않는 문자로 치환하는 방법으로 해결 가능
- 내장 도큐먼트 구조가 복잡해질수록 일치하는 내장 도큐먼트를 찾기가 어려워짐
- i.g. 저장된 블로그 게시물에서 5점 이상을 받은 Joe의 댓글을 찾는다고 가정했을 때 다음과 같이 쿼리를 작성
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 게시물 모델링 예시 | |
// 목표는 블로그 게시물에서 5점 이상을 받은 Joe의 댓글을 조회 | |
{ | |
"content": "...", | |
"comments": [ | |
{ | |
"author": "joe", | |
"score": 3, | |
"comment": "nice post" | |
}, | |
{ | |
"author": "mary", | |
"score": 6 | |
"comment": "terrible post" | |
} | |
] | |
} | |
// 재아 도큐먼트가 쿼리 도큐먼트 전체와 일치해야 하는데 아래 쿼리도 큐먼트는 "comment" 키가 없으므로 | |
// db.blog.find({"comments": {"author": "joe", "score": {"$gte": 5}}})로 쿼리 불가능 | |
// 쿼리 도큐먼트에서 댓글의 score 조건과 author 조건은 댓글 배열 내의 각기 다른 도큐먼트와 일치하기 때문에 | |
// db.blog.find({"comments.author": "joe", "comments.score": {"$gte": 5}})는 모두 반환하기 때문에 목표에 안 맞음 | |
// 모든 키를 지정하지 않고도 조건을 정확하게 묶기 위해서는 "$elemMatch"를 사용 | |
// 해당 조건절은 조건을 부분적으로 지정해 배열 내에서 하나의 내장 도큐먼트를 찾게 해줌 | |
db.blog.find({"comments": {"$elemMatch": {"author": "joe", "score": {"$gte": 5}}}}) |
부연 설명
- "$elemMatch"를 사용해 조건을 `그룹화` 가능하고 해당 기능은 내장 도큐먼트에서 두 개 이상의 키의 조건 일치 여부를 확인할 때만 필요
4. $where 쿼리
- key-value 쌍만으로 꽤 다양한 쿼리를 할 수 있지만 정확하게 표현할 수 없는 쿼리도 있음
- 이때 "$where" 절을 사용해 임의의 자바스크립트를 쿼리의 일부분으로 실행하면 모든 거의 쿼리를 표현 가능
- 따라서 보안상의 이유로 "$where" 절 사용을 제한해야 함
- "$where" 절은 도큐먼트 내 두 키의 값을 비교하는 쿼리에 가장 자주 쓰임
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 다음과 같은 도큐먼트가 있다고 가정 | |
db.foo.insertOne({"apple": 1, "banana": 6, "peach": 3}) | |
db.foo.insertOne({"apple": 5, "spinach": 4, "watermelon": 4}) | |
// 두 필드의 값이 동일한 도큐먼트를 반환 | |
// 함수가 true를 반환하면 해당 도큐먼트는 결과 셋에 포함되고 false를 반환하면 포함되지 않음 | |
db.foo.find({"$where": function() { | |
for (var current in this) { | |
for (var other in this) { | |
if (current != other && this[current] == this[other]) { | |
return true; | |
} | |
} | |
} | |
return false | |
}}); |
- "$where" 쿼리는 일반 쿼리보다 훨씬 느리니 반드시 필요한 경우가 아니면 사용 안 하는 것을 권장
- "$where" 절 실행 시 각 도큐먼트는 ABSON에서 자바스크립트 객체로 변환되기 때문에 오래 걸림
- 또한 "$where" 절에는 인덱스를 쓸 수 없음
- 따라서 "$where" 절은 달리 쿼리 할 방법이 전혀 없을 때만 사용해야 함
- "$where" 절을 다른 쿼리 필터와 함께 사용하면 성능 저하를 줄일 수 있음
- 가능한 한 "$where" 절이 아닌 조건은 인덱스로 거르고, "$where" 절은 결과를 세부적으로 조정할 때 사용하는 것을 권장
- $expr을 사용하면 자바스크립트를 실행하지 않아 더 빨리 쿼리할 수 있으므로 가능한 한 $where 대신 $expr을 사용하는 것을 권장
5. 커서
- 데이터베이스는 커서를 사용해 find의 결과를 반환
- 일반적으로 클라이언트 측의 커서 구현체는 쿼리의 최종 결과를 강력히 제어하게 해 줌
- 결과 개수를 제한하거나, 결과 중 몇 개를 건너뛰거나, 여러 키를 조합한 결과를 어떤 방향으로든 정렬하는 등 다양하게 조작할 수 있음
- 셸에서 커서를 생성하려면 컬렉션에 도큐먼트를 집어넣고 쿼리 한 후 결과를 지역 변수에 할당
- 이렇게 하면 결과를 한 번에 하나씩 볼 수 있다는 장점이 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (i = 0; i < 100; i++) { | |
db.collection.insertOne({x: i}); | |
} | |
var cursor = db.collection.find(); |
- 결과를 얻으려면 커서의 next 메서드를 사용하고, 다른 결과가 있는지 확인하려면 hasNext를 사용하며 결과를 확인하는 반복문은 일반적으로 다음과 같음
- cursor.hasNext()는 다음 결과가 존재하는지 확인하고 cursor.next()는 그 결과를 가져옴
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
while (cursor.hasNext()) { | |
obj = cursor.next(); | |
// 사용자 정의 작업 수행 | |
} |
- 또한 cursor 클래스는 자바스크립트의 반복자 인터페이스를 구현했으므로 forEach 반복문에 사용할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var cursor = db.people.find(); | |
cursor.forEach(function(x) { | |
print(x.name); | |
}); |
- find()를 호출할 때 셸이 데이터베이스를 즉시 쿼리하지는 않으며 결과를 요청하는 쿼리를 보낼 때까지 대기함
- 따라서 쿼리 하기 전에 옵션을 추가 가능
- 또한 cursor 객체상의 거의 모든 메서드가 커서 자체를 반환하므로 옵션을 어떤 순서로든 이어 쓸 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 다음 쿼리들은 모두 동일하게 작동함 | |
// 함수들은 쿼리를 만들기만 했을 뿐 이 시점에 쿼리는 아직 수행되지 않음 | |
var cursor = db.foo.find().sort({"x": 1}).limit(1).skip(10); | |
var cursor = db.foo.find().limit(1).sort({"x": 1}).skip(10); | |
var cursor = db.foo.find().skip(10).limit(1).sort({"x": 1}); | |
// 아래처럼 쿼리를 호출하는 시점에 수행됨 | |
cursor.hasNext(); |
부연 설명
- 셸은 next나 hasNext 메서드 호출 시 서버 왕복 횟수를 줄이기 위해, 한 번에 처음 100개 또는 4MB 크기의 결과를 가져옴
- 클라이언트가 첫 번째 결과 셋을 살펴본 후에, 셀이 데이터베이스에 다시 접근해 더 많은 결과를 요청
- getMore 요청은 기본적으로 커서에 대한 식별자를 가지며, 데이터베이스가 다음 배치를 반환하도록 요구하며 프로세스는 모든 결과를 반환해 커서가 소진될 때까지 계속됨
5.1 제한, 건너뛰기, 정렬
- 가장 일반적인 쿼리 옵션으로는 반환받는 결과 개수를 제한하거나, 몇 개의 결과를 건너뛰거나, 결과를 정렬하는 옵션이 있음
- 옵션은 데이터베이스에 전송되기 전에 추가해야 함
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 결과 개수를 제한하려면 find 호출에 limit 함수를 연결 | |
// 3개의 결과만 반환받는 예제 | |
db.c.find().limit(3) | |
// skip은 limit와 유사하게 작동 | |
db.c.find().skip(3) | |
// "username"은 오름차순으로, "age"는 내림차순으로 정렬 | |
db.c.find().sort({username: 1, age: -1}) | |
// 온라인 상점에 고객이 들어와서 mp3를 검색했을 때 | |
// 가격을 내림차순으로 정렬해 한 페이지당 50개씩 결과 조회 | |
db.stock.find({"desc": "mp3"}).limit(50).sort({"price": -1}) | |
// 다음 페이지를 클릭하면 더 많은 결과가 보이게 하려면 쿼리에 skip을 추가해서 처음 50개의 결과를 건너 뛸 수 있음 | |
db.stock.find({"desc": "mp3"}).limit(50).skip(50).sort({"price": -1}) |
비교 순서
- 몽고DB에는 데이터형을 비교하는 위계 구조가 있음
- 정수형과 불리언형, 문자열형과 null형처럼 때로는 하나의 키에 여러 데이터형 값을 저장할 수 있음
- 데이터형이 섞여 있는 키는 미리 정의된 순서에 따라 정렬됨
- 데이터형 정렬 순서를 최솟값에서 최댓값 순으로 나타내면 다음과 같음
- 최솟값
- null
- 숫자
- 문자열
- 객체/도큐먼트
- 배열
- 이진 데이터
- 객체 ID
- 불리언
- 날짜
- 타임스탬프
- 정규 표현식
- 최댓값
5.2 많은 수의 건너뛰기 피하기
- 도큐먼트 수가 적을 때는 skip을 사용해도 무리가 없지만 skip은 생략된 결과물을 모두 찾아 폐기하므로 결과가 많이 느려짐
- 대부분의 데이터베이스는 skip을 위해 인덱스 안에 메타데이터를 저장하지만 몽고DB (4.2.1 버전)는 아직 해당 기능을 지원하지 않음
- 따라서 많은 수의 건너뛰기는 피해야 함
skip을 사용하지 않고 페이지 나누기
- limit을 사용해 첫 번째 페이지를 반환하고, 다음 페이지들은 첫 페이지부터 오프셋을 주어 반환하면 가장 쉽게 페이지를 나눌 수 있지만 쿼리에 따라 skip을 사용하지 않는 방법을 찾을 수 있음
- i.g. "date"를 내림차순으로 정렬해 도큐먼트를 표시한다고 가정
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 첫 페이지를 구하고 | |
var page1 = db.foo.find().sort({"date": -1}).limit(100) | |
// 마지막 도큐먼트의 "date" 값을 사용해 다음 페이지를 가져옴 | |
var latest = null; | |
// 첫 페이지 보여주기 | |
while (page1.hasNext()) { | |
latest = page1.next() | |
display(latest) | |
} | |
// 다음 페이지 가져오기 | |
var page2 = db.foo.find({"date": {"$lt": latest.date}}); | |
page2.sort({"date": -1}).limit(100); |
랜덤으로 도큐먼트 찾기
- 컬렉션에서 랜덤으로 도큐먼트를 가져오는 방법은 자주 문제가 발생함
- 단순하고 느린 방법으로는 도큐먼트의 개수를 세고 find를 실행한 뒤 0과 컬렉션 크기 사이의 수를 랜덤으로 뽑아 그 개수만큼 건너뛰는 방법 (매우 비효율적)
- 훨씬 효율적인 방법으로는 도큐먼트를 입력할 때 랜덤 키를 별도로 추가하는 방법 i.g. 셸을 사용한다면 Math.random() 함수를 사용
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
db.people.insertOne({"name": "joe", "random": Math.rnadom()}) | |
db.people.insertOne({"name": "john", "random": Math.rnadom()}) | |
db.people.insertOne({"name": "jim", "random": Math.rnadom()}) | |
// 컬렉션에서 랜덤으로 도큐먼트를 찾고 싶으면 skip을 사용하는 대신 랜덤 수를 계산해 쿼리 조건으로 사용 | |
var random = Math.random() | |
result = db.people.findOne({"random": {"$gt": random}}) | |
// random 값이 컬렉션 내 모든 "random" 값보다 클 때는 빈 결과를 반환하는데 이는 아래와 같이 방지 가능 | |
if (result == null) { | |
result = db.people.findOne({"random": {"$lte": random}}) | |
... | |
} |
5.3 종료되지 않는 커서
- 커서에는 두 가지 측면이 있음
- 클라이언트가 보는 커서
- 클라이언트 커서가 나타내는 데이터베이스 커서
- 서버 측에서 보면 커서는 메모리와 리소스를 점유함
- 커서가 더는 가져올 결과가 없거나 클라이언트로부터 종료 요청을 받으면 데이터베이스는 점유하고 있던 리소스를 해제함
- 그러면 데이터베이스가 리소스를 다른 작업에 사용할 수 있으므로 커서도 신속하게 해제해야 함
- 서버 커서를 종료하는 몇 가지 조건이 있음
- 커서는 조건에 일치하는 결과를 모두 살펴본 후에 스스로 정리함
- 커서가 클라이언트 측에서 유효 영역을 벗어나면 드라이버는 데이터베이스에 메시지를 보내 커서를 종료해도 된다고 알림
- 사용자가 아직 결과를 다 살펴보지 않았고, 커서가 여전히 유효 영역 내에 있더라도 10분 동안 활동이 없으면 데이터베이스 커서는 자동으로 죽음
- 사용자가 몇 분 동안 결과를 기다리게 하는 애플리케이션은 거의 없으므로 `타임아웃에 의한 종료`는 바람직한 동작
- 하지만 종종 커서를 오래 남겨두고 싶을 때가 있으므로 많은 드라이버는 데이터베이스가 커서를 타임아웃시키지 못하게 하는 immortal이라는 함수를 제공
- 커서의 타임아웃을 비활성화했다면 반드시 결과를 모두 살펴보거나 커서를 명확히 종료해야 함
- 그렇지 않으면 커서는 데이터베이스에 남아 서버가 재시작할 때까지 리소를 차지함
참고
몽고DB 완벽 가이드 3판 - 한빛미디어
반응형
'DB > 몽고DB 완벽 가이드 3판' 카테고리의 다른 글
[6장] 특수 인덱스와 컬렉션 유형 (0) | 2025.04.10 |
---|---|
[5장] 인덱싱 (0) | 2025.04.04 |
[3장] 도큐먼트 생성, 갱신, 삭제 (0) | 2025.03.28 |
[2장] 몽고DB 기본 (0) | 2025.03.27 |
[1장] 몽고DB 소개 (0) | 2025.03.26 |