Docs Menu
Docs Home
/
데이터베이스 매뉴얼
/ /

고유 인덱스 및 스키마 유효성 검사

데이터베이스 애플리케이션 설계에 부합하도록 하기 위해 전략적인 인덱스를 생성하여 인덱스 속성과 스키마 유효성 검사 결합할 수 있습니다.

사용자의 재정을 요약하는 애플리케이션 예로 들어 보겠습니다. 애플리케이션 의 기본 페이지에는 사용자의 ID 와 애플리케이션 과 동기화된 모든 은행 계정의 잔액이 표시됩니다.

애플리케이션 users 컬렉션 에 사용자 정보를 저장합니다. users 컬렉션 에는 다음 스키마 가진 문서가 포함되어 있습니다.

db.users.insertOne( {
_id: 1,
name: { first: "john", last: "smith" },
accounts: [
{ balance: 500, bank: "abc", number: "123" },
{ balance: 2500, bank: "universal bank", number: "9029481" }
]
} )

애플리케이션 다음 규칙이 필요합니다.

  • 사용자는 애플리케이션 에 등록할 수 있지만 은행 계좌를 동기화 수는 없습니다.

  • 사용자는 banknumber 필드로 계정을 식별합니다.

  • 사용자는 서로 다른 두 사용자에 대해 동일한 계정을 등록할 수 없습니다.

  • 사용자는 동일한 사용자에 대해 동일한 계정을 여러 번 등록할 수 없습니다.

문서를 애플리케이션의 규칙에 따라 제한하도록 데이터베이스 설계하려면 다음 절차에 따라 데이터베이스 에서 고유 인덱스 와 스키마 유효성 검사 결합합니다.

1

애플리케이션의 규칙을 시행하다 하려면 다음 특성을 가진 accounts.bankaccounts.number 필드에 인덱스 생성합니다.

  • bank number 및 필드가 반복되지 않도록 하려면 인덱스 를 고유하게 만듭니다.

  • 여러 필드의 인덱싱 허용하려면 인덱스를 복합 인덱스로 만듭니다.

  • 배열 내부의 문서에 대한 인덱싱 허용하려면 유형의 인덱스 멀티키로 만듭니다.

따라서 다음 사양 및 옵션을 사용하여 복합 멀티키 고유 인덱스 생성합니다.

const specification = { "accounts.bank": 1, "accounts.number": 1 };
const options = { name: "Unique Account", unique: true };
db.users.createIndex(specification, options); // Unique Account
2

현재 상태 의 인덱스 모든 문서를 인덱싱합니다. 그러나 이 구현 accounts.bank 또는 accounts.number 필드가 누락된 문서를 삽입할 때 오류가 발생할 수 있습니다.

예시 들어 users 컬렉션 에 다음 데이터를 삽입해 봅니다.

const user1 = { _id: 1, name: { first: "john", last: "smith" } };
const user2 = { _id: 2, name: { first: "john", last: "appleseed" } };
const account1 = { balance: 500, bank: "abc", number: "123" };
db.users.insertOne(user1);
db.users.insertOne(user2);
{ acknowledged: true, insertedId: 1 }
MongoServerError: E11000 duplicate key error collection: test.users index: Unique Account dup key: { accounts.bank: null, accounts.number: null }

지정된 필드가 하나 이상 누락된 문서 인덱싱된 컬렉션 에 삽입하려고 하면 MongoDB 다음을 수행합니다.

  • 삽입된 문서 에 누락된 필드를 채웁니다.

  • 값을 다음으로 설정합니다. null

  • 인덱스 에 항목을 추가합니다.

accounts.bankaccounts.number 필드 없이 user1 을(를) 삽입하면 MongoDB 해당 필드를 null(으)로 설정하고 고유 인덱스 항목을 추가합니다. user2와 같이 두 필드 하나도 없는 나중에 삽입하면 중복 키 오류가 발생합니다.

이를 방지하려면 부분 필터하다 표현식 사용하여 인덱스 두 필드가 모두 포함된 문서만 포함되도록 합니다. 자세한 내용은 고유 제약 조건이 있는 부분 인덱스를 참조하세요. 다음 옵션을 사용하여 인덱스 다시 생성합니다.

const specification = { "accounts.bank": 1, "accounts.number": 1 };
const optionsV2 = {
name: "Unique Account V2",
partialFilterExpression: {
"accounts.bank": { $exists: true },
"accounts.number": { $exists: true }
},
unique: true
};
db.users.drop( {} ); // Delete previous documents and indexes definitions
db.users.createIndex(specification, optionsV2); // Unique Account V2

accounts.bankaccounts.number 필드를 포함하지 않는 두 명의 사용자를 삽입하여 새 인덱스 정의를 테스트합니다.

db.users.insertOne(user1);
db.users.insertOne(user2);
{ acknowledged: true, insertedId: 1 }
{ acknowledged: true, insertedId: 2 }
3

서로 다른 두 사용자에 대해 동일한 계정을 등록할 수 없도록 다음 코드를 테스트합니다.

/* Cleaning the collection */
db.users.deleteMany( {} ); // Delete only documents, keep indexes definitions
db.users.insertMany( [user1, user2] );
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user2._id }, { $push: { accounts: account1 } } );
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
MongoServerError: E11000 duplicate key error collection: test.users index: Unique Account V2 dup key: { accounts.bank: "abc", accounts.number: "123" }

두 번째 updateOne 명령은 두 명의 개별 사용자에 대해 동일한 계정을 추가할 수 없으므로 오류를 올바르게 반환합니다.

데이터베이스 동일한 사용자에 대해 동일한 계정을 여러 번 추가할 수 없는지 테스트합니다.

/* Cleaning the collection */
db.users.deleteMany( {} ); // Delete only documents, keep indexes definitions
db.users.insertMany( [user1, user2] ); // Re-insert test documents
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.findOne( { _id: user1._id } );
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
_id: 1,
name: { first: 'john', last: 'smith' },
accounts: [
{ balance: 500, bank: 'abc', number: '123' },
{ balance: 500, bank: 'abc', number: '123' }
]

반환된 코드는 데이터베이스 동일한 사용자에게 동일한 계정을 여러 번 잘못 추가했음을 보여줍니다. 이 오류는 MongoDB 인덱스가 동일한 문서 를 가리키는 동일한 키 값을 가진 완전히 동일한 항목을 복제하지 않기 때문에 발생합니다.

사용자에게 account1 를 두 번째로 삽입하면 MongoDB 인덱스 항목 생성하지 않으므로 중복 값이 없습니다. 애플리케이션 디자인을 효과적으로 구현 하려면 동일한 사용자에게 동일한 계정을 여러 번 추가하려고 시도하는 경우 데이터베이스 오류를 반환해야 합니다.

4

애플리케이션 동일한 사용자에게 동일한 계정을 여러 번 추가하는 것을 거부하도록 하려면 스키마 유효성 검사 구현 . 다음 코드에서는 $expr 연산자 사용하여 배열 내의 항목이 고유한지 확인하는 표현식 쓰기 (write) .

const accountsSet = {
$setIntersection: {
$map: {
input: "$accounts",
in: { bank: "$$this.bank", number: "$$this.number" }
}
}
};
const uniqueAccounts = {
$eq: [ { $size: "$accounts" }, { $size: accountsSet } ]
};
const accountsValidator = {
$expr: {
$cond: {
if: { $isArray: "$accounts" },
then: uniqueAccounts,
else: true
}
}
};

{ $isArray: "$accounts" }true이면 문서 에 accounts 배열 존재하며 MongoDB uniqueAccounts 유효성 검사 검사 로직을 적용합니다. 문서 로직을 통과하면 유효합니다.

uniqueAccounts 표현식 원래 accounts 배열 의 크기를 매핑된 버전의 에 의해 accountsSet $setIntersection 생성된 의 크기와 accounts 비교합니다.

  • $map 함수는 accounts accounts.bank 및 필드만 accounts.number 포함하도록 배열 의 각 항목을 변환합니다.

  • $setIntersection 함수는 매핑된 배열 설정하다 으로 처리하여 중복을 제거합니다.

  • $eq 함수는 원본 accounts 배열 과 중복 제거된 accountsSet 배열의 크기를 비교합니다.

두 크기가 동일하고 모든 항목이 accounts.bankaccounts.number로 고유한 경우 유효성 검사 true를 반환합니다. 그렇지 않은 경우 중복이 존재하며 오류와 함께 유효성 검사 실패합니다.

스키마 유효성 검사 검사를 테스트하여 데이터베이스 동일한 사용자에게 동일한 계정을 여러 번 추가하는 것을 허용하지 않는지 확인할 수 있습니다.

/* Cleaning the collection */
db.users.drop( {} ); // Delete documents and indexes definitions
db.runCommand( {
collMod: "users", // update collection to use schema validation
validator: accountsValidator
} );
db.users.insertMany( [user1, user2] );
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: 1,
details: {
operatorName: '$expr',
specifiedAs: {
'$expr': {
'$cond': {
if: { '$and': '$accounts' },
then: { '$eq': [ [Object], [Object] ] },
else: true
}
}
},
reason: 'expression did not match',
expressionResult: false
}
}

두 번째 updateOne() 명령은 Document failed validation 오류를 반환하며, 이는 데이터베이스 동일한 사용자에게 동일한 계정을 여러 번 추가하려는 시도를 거부했음을 나타냅니다.

돌아가기

쿼리 선택성 보장

스킬 배지 획득

무료로 '인덱싱 설계 기본'을 마스터하세요!

자세한 내용을 알아보세요.

이 페이지의 내용