背景:文檔結構與主鍵核心機制
1 ) 文檔主鍵(_id)的本質
技術簡注:_id是MongoDB文檔的全局唯一標識符,功能等同於SQL數據庫的主鍵。
- 強制性唯一標識
- 每篇文檔必須包含
_id字段,值在集合內絕對唯一 - 支持除數組外所有數據類型:整型、UUID、字符串或複合主鍵(嵌套文檔)
- ObjectId自動化生成
|
字節段
|
長度
|
作用
|
|
時間戳
|
4
|
Unix秒級時間( |
|
機器ID
|
5
|
生成主機的唯一標識符
|
|
計數器
|
3
|
同一秒內的防重複序列
|
// 生成與解析ObjectId
const newId = new ObjectId();
console.log(newId.getTimestamp()); // 輸出:ISODate("2023-10-05T08:30:00Z")
- 時鐘偏差風險
分佈式系統中客户端時鐘不同步時,生成順序可能與創建時間順序不一致。
方法:文檔創建操作核心技術
1 ) 單文檔插入(insertOne)
db.accounts.insertOne(
{
_id: "account1", // 顯式主鍵(可選)
name: "Alice",
balance: 1000
},
{ writeConcern: { w: "majority" } } // 安全寫級別配置
)
特性:
- 自動建集合(目標集合不存在時)
- 主鍵衝突報錯:
E11000 duplicate key error - 返回結構:
{ acknowledged: true, insertedId: "account1" }
2 ) 批量插入(insertMany)
// 無序寫入(ordered:false優化吞吐量)
db.accounts.insertMany(
[
{ _id: "dupKey", name: "ErrorDoc" },
{ name: "ValidDoc" } // 此文檔成功寫入
],
{ ordered: false }
)
|
操作模式
|
錯誤處理機制
|
適用場景
|
|
|
遇錯立即停止
|
事務性連續寫入
|
|
|
繼續執行非衝突文檔
|
大數據批量導入
|
3 ) 通用插入命令(insert)
// 單文檔與批量統一接口
db.accounts.insert({ name: "SingleDoc" }) // 單文檔
db.accounts.insert([ { _id: "doc1" }, { _id: "doc2" } ]) // 多文檔
與insertMany差異:
- 支持
explain()執行計劃分析 - 返回格式:
WriteResult({ "nInserted": N })
- save命令的智能邏輯
// 根據_id存在性自動選擇操作
db.accounts.save({ name: "New" }) // 無_id → insert操作
db.accounts.save({ _id: "exist", name: "Update" }) // 有_id → update操作
命令對比與特性總結
|
命令
|
適用場景
|
返回結果格式
|
是否支持 |
|
|
單文檔插入
|
|
❌
|
|
|
多文檔插入
|
|
❌
|
|
|
單/多文檔插入
|
|
✅
|
要點
insertOne適合單文檔事務寫入insertMany+ordered:false提升批量吞吐量insert支持執行計劃分析save根據_id自動路由操作類型
ObjectId 的顯式操作
// 生成新ObjectId
const newId = ObjectId() // 輸出:ObjectId("507f1f77bcf86cd799439011")
// 解析時間戳
const creationTime = newId.getTimestamp() // 返回ISO日期字符串
// 從字符串構造ObjectId
const customId = ObjectId("507f1f77bcf86cd799439011")
案例:複合主鍵實戰與陷阱
1 ) 複合主鍵定義
db.accounts.insert({
_id: {
type: "savings", // 字段順序敏感!
number: "001"
},
name: "ComplexKey"
})
2 ) 主鍵順序陷阱
// 字段順序變化生成新主鍵!
db.accounts.insert({
_id: {
number: "001", // 順序顛倒
type: "savings"
},
name: "OrderMatters"
}) // 成功寫入(非衝突)
核心規則:
要點
- 複合主鍵的唯一性由字段鍵值對的有序結構決定
- 字段順序變化將被視為不同主鍵
- 生產環境建議固定字段順序(如Schema強制約束)
方法:文檔讀取核心技術棧
1 ) find查詢引擎與遊標
// 鏈式操作:查詢→排序→分頁
const cursor = db.accounts.find({ balance: { $gt: 500 } })
cursor.sort({ name: 1 })
.skip(10)
.limit(5)
.forEach(printjson);
2 ) 投射(Projection)性能優化
技術簡注:投射通過字段篩選減少網絡傳輸。
// 只返回name和balance(排除_id)
db.accounts.find(
{ type: "checking" },
{ name: 1, balance: 1, _id: 0 } // 1=包含, 0=排除
)
性能影響:
- 減少70%+網絡傳輸開銷
- 嵌套文檔支持:
{ "contact.phone": 1 }
3 ) 查詢操作符體系
|
類型
|
操作符示例
|
作用
|
|
比較操作符
|
|
值比較
|
|
邏輯操作符
|
|
條件組合
|
|
數組操作符
|
|
數組字段查詢
|
|
元素操作符
|
|
字段存在性/類型檢查
|
案例:NestJS集成MongoDB實戰
1 ) 數據模型定義
// account.schema.ts
@Schema()
export class Account extends Document {
@Prop({ type: Object, required: true })
_id: { type: string; number: string }; // 複合主鍵
@Prop({ required: true })
name: string;
@Prop({ default: 0 })
balance: number;
}
2 ) 服務層CRUD實現
@Injectable()
export class AccountsService {
constructor(
@InjectModel(Account.name) private accountModel: Model<Account>
) {}
// 批量插入(無序)
async bulkCreate(accounts: AccountDto[]) {
return this.accountModel.insertMany(accounts, { ordered: false });
}
// 複合主鍵查詢
async findByCompositeKey(type: string, number: string) {
return this.accountModel.findOne({
_id: { type, number } // 字段順序必須一致!
});
}
}
最佳實踐與錯誤處理
1 ) 錯誤處理標準化
|
操作
|
成功響應格式
|
主鍵衝突錯誤
|
|
|
|
|
|
|
|
|
// NestJS錯誤處理範例
try {
await collection.insertMany(data, { ordered: false });
} catch (error) {
if (error.code === 11000) { // 主鍵衝突代碼
const dupKeys = error.keyValue;
throw new ConflictException(`重複主鍵: ${dupKeys}`);
}
}
2 )核心性能優化策略
- 主鍵設計:
- 優先使用ObjectId規避唯一性管理負擔
- 複合主鍵需嚴格字段順序管理
- 寫入優化:
/* 性能診斷語句 */
db.runCommand({
explain: { insert: "accounts", documents: [...] },
verbosity: "executionStats"
})
- 金融系統啓用安全寫:
{ w: "majority", j: true }(日誌持久化) - 控制寫入操作的確認機制,通過
w參數指定:
w: 1(默認):主節點確認即返回w: "majority":多數節點確認j: true:寫入日誌後返回
- 查詢優化:
- 投射過濾減少70%+網絡傳輸
- 使用
snapshot()防止重複讀取,batchSize()控制網絡傳輸量
db.accounts.find().snapshot().batchSize(50)
- 遊標分批加載:
cursor.batchSize(50)
全局最佳實踐總結
- 大規模寫入使用
insertMany+ordered:false - 複合主鍵字段順序需通過Schema強制約束
- 查詢必用投射減少傳輸開銷
- 金融系統啓用
writeConcern: majority - 錯誤處理優先識別
code:11000主鍵衝突