데이터베이스 애플리케이션 설계에 부합하도록 하기 위해 전략적인 인덱스를 생성하여 인덱스 속성과 스키마 유효성 검사 결합할 수 있습니다.
이 작업에 대하여
사용자의 재정을 요약하는 애플리케이션 예로 들어 보겠습니다. 애플리케이션 의 기본 페이지에는 사용자의 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" } ] } )
애플리케이션 다음 규칙이 필요합니다.
사용자는 애플리케이션 에 등록할 수 있지만 은행 계좌를 동기화 수는 없습니다.
사용자는
bank
및number
필드로 계정을 식별합니다.사용자는 서로 다른 두 사용자에 대해 동일한 계정을 등록할 수 없습니다.
사용자는 동일한 사용자에 대해 동일한 계정을 여러 번 등록할 수 없습니다.
문서를 애플리케이션의 규칙에 따라 제한하도록 데이터베이스 설계하려면 다음 절차에 따라 데이터베이스 에서 고유 인덱스 와 스키마 유효성 검사 결합합니다.
단계
다중 속성 인덱스 만들기
애플리케이션의 규칙을 시행하다 하려면 다음 특성을 가진 accounts.bank
및 accounts.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
만들기 partialFilterExpression
현재 상태 의 인덱스 모든 문서를 인덱싱합니다. 그러나 이 구현 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.bank
및 accounts.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.bank
및 accounts.number
필드를 포함하지 않는 두 명의 사용자를 삽입하여 새 인덱스 정의를 테스트합니다.
db.users.insertOne(user1); db.users.insertOne(user2);
{ acknowledged: true, insertedId: 1 } { acknowledged: true, insertedId: 2 }
데이터베이스 구현 테스트
서로 다른 두 사용자에 대해 동일한 계정을 등록할 수 없도록 다음 코드를 테스트합니다.
/* 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 인덱스 항목 생성하지 않으므로 중복 값이 없습니다. 애플리케이션 디자인을 효과적으로 구현 하려면 동일한 사용자에게 동일한 계정을 여러 번 추가하려고 시도하는 경우 데이터베이스 오류를 반환해야 합니다.
스키마 유효성 검사 설정
애플리케이션 동일한 사용자에게 동일한 계정을 여러 번 추가하는 것을 거부하도록 하려면 스키마 유효성 검사 구현 . 다음 코드에서는 $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.bank
및 accounts.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
오류를 반환하며, 이는 데이터베이스 동일한 사용자에게 동일한 계정을 여러 번 추가하려는 시도를 거부했음을 나타냅니다.