跳到主要内容

MongoDB

MongoDB 的核心优势

  1. 灵活模式 json 文档
  2. 高可用性 副本集
  3. 可扩展性 sharded cluster 分片集群,可以将数据分散存储到多个分片 shard 上来实现高扩展性。
    使 MongoDB 具备了横向扩展的能力。

部署方式​

  1. 主从复制 master-slave ,目前不推荐使用了。
  2. 副本集 replica set,将数据复制多份,不同服务器保存同一份数据,在出现故障时自动切换,实现故障转移。
  3. 分片模式 sharding,适合处理大量数据,它将数据分开存储,不同的服务器保存不同的数据,所有服务器数据的总和即为整个数据集。(* 追求高性能*)

https://www.cnblogs.com/yanxinjiang/p/14600568.html

https://zhangquan.me/2023/03/26/mongodb-fen-pian-ji-qun-ji-zhi-ji-yuan-li/

副本集部署方式​

增删改查基本操作​

  • Adhoc queries 临时查询
  • Data transformations 数据转换
  • Document join support 文档连接 $lookup $unionwith
  • Graph and geospatial queries 图形和地理空间查询 $geoWithin $geoNear graphLookup
  • Full-text search 全文检索 $search
  • Semantic search 语义搜索 $vectorSearch
  • Indexing 索引
  • On-demand materialized views 按需创建实体索引 $out $merge
  • Time series analysis 时间序列分析

1. 查询数据库列表​

db.adminCommand({ listDatabases: 1 });

2. $unwind​

db.inventory.insertMany( [
{ prodId: 100, price: 20, quantity: 125 },
{ prodId: 101, price: 10, quantity: 234 },
{ prodId: 102, price: 15, quantity: 432 },
{ prodId: 103, price: 17, quantity: 320 }
] );

db.orders.insertMany( [
{ orderId: 201, custid: 301, prodId: 100, numPurchased: 20 },
{ orderId: 202, custid: 302, prodId: 101, numPurchased: 10 },
{ orderId: 203, custid: 303, prodId: 102, numPurchased: 5 },
{ orderId: 204, custid: 303, prodId: 103, numPurchased: 15 },
{ orderId: 205, custid: 303, prodId: 103, numPurchased: 20 },
{ orderId: 206, custid: 302, prodId: 102, numPurchased: 1 },
{ orderId: 207, custid: 302, prodId: 101, numPurchased: 5 },
{ orderId: 208, custid: 301, prodId: 100, numPurchased: 10 },
{ orderId: 209, custid: 303, prodId: 103, numPurchased: 30 }
] );
// 根据 orders 创建 sales 集合
db.createView("sales", "orders", [
{
$lookup: // 使用 orders.prodId 匹配 inventory.prodId
{
from: "inventory",
localField: "prodId",
foreignField: "prodId",
as: "inventoryDocs" // 匹配到的数据添加到 inventoryDocs 集合中
}
},
{
$project: // 选择显示的字段
{
_id: 0, // 不显示
prodId: 1, // 显示
orderId: 1,
numPurchased: 1,
price: "$inventoryDocs.price" // 聚合成 price 字段(数组)
}
},
{
$unwind: "$price" // 将数组展开
}
]);

$unwind 的作用: 展开数组字段

假设 collection 的数据如下所示

[
{ "_id": 1, "product": "A", "price": [100, 200, 300] },
{ "_id": 2, "product": "B", "price": [400, 500] },
{ "_id": 3, "product": "C", "price": [] },
{ "_id": 4, "product": "D" }
]

在使用 $unwind 函数之后

[
{ "_id": 1, "product": "A", "price": 100 },
{ "_id": 1, "product": "A", "price": 200 },
{ "_id": 1, "product": "A", "price": 300 },
{ "_id": 2, "product": "B", "price": 400 },
{ "_id": 2, "product": "B", "price": 500 }
]

3. 创建视图,并排序​

db.places.insertMany([
{ _id: 1, category: "café" },
{ _id: 2, category: "cafe" },
{ _id: 3, category: "cafE" }
]);

db.places.find();

根据 places 创建视图 placesView , 只保留 category 字段,并且根据 fr 进行排序

db.createView(
"placesView",
"places",
[{
$project: {
category: 1
}
}],
{
collation: {
locale: "fr", // 指定语言环境为 法语
strength: 1 // 排序强度, 1 表示只比较基本字符.
}
}
);

统计 category = cafe 的文档数量

db.placesView.countDocuments({
category: "cafe"
});

4. 视图修改​

先删除,再新建

db.createView("places-view-modify", "places", [
{
$match: {
category: "cafe"
}
}
]);

db["places-view-modify"].find();

db["places-view-modify"].drop();

直接修改视图

db.runCommand(
{
collMod: "places-view-modify",
viewOn: "places",
"pipeline": [{
$match: {
category: "cafE"
}
}]
}
);

5. 物化视图​

// 物化视图  $merge $out 的结果
db.bakesales.insertMany( [
{ date: new ISODate("2018-12-01"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2018-12-02"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("90") },
{ date: new ISODate("2018-12-02"), item: "Cake - Red Velvet", quantity: 10, amount: new NumberDecimal("200") },
{ date: new ISODate("2018-12-04"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
{ date: new ISODate("2018-12-04"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2018-12-05"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-25"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-25"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-26"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
{ date: new ISODate("2019-01-26"), item: "Cake - Carrot", quantity: 2, amount: new NumberDecimal("36") },
{ date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-27"), item: "Pie - Chocolate Cream", quantity: 1, amount: new NumberDecimal("20") },
{ date: new ISODate("2019-01-27"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("80") },
{ date: new ISODate("2019-01-27"), item: "Tarts - Apple", quantity: 3, amount: new NumberDecimal("12") },
{ date: new ISODate("2019-01-27"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
{ date: new ISODate("2019-01-27"), item: "Cake - Carrot", quantity: 5, amount: new NumberDecimal("36") },
{ date: new ISODate("2019-01-27"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-28"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
{ date: new ISODate("2019-01-28"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-28"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
] );

db.bakesales.find();

// 定义了一个包含每月累计销售信息的物化视图
updateMonthlySales = function(startDate) {
db.bakesales.aggregate([
{
$match: {
date: {
$gte: startDate // 大于等于传入的日期的数据
}
}
},
{
$group: {
_id: {
$dateToString: { // 根据 date 字段的 %Y-%m 进行分组
format: "%Y-%m",
date: "$date"
}
},
sales_quantity: {
$sum: "$quantity" // 新增一个 sales_quantity 字段
},
sales_amount: {
$sum: "$amount" // 新增 sales_amount 字段
}
}
},
{
$merge: {
into: "monthlybakesales", // 将数据输出到 monthlybakesales 集合中
whenMatched: "replace" // 当文档已经存在于目的集合时,替换目的集合中的文档
}
}
]);
};


updateMonthlySales(new ISODate("1970-01-01"));
db.monthlybakesales.find();

更新物化视图

db.bakesales.insertMany( [
{ date: new ISODate("2019-01-28"), item: "Cake - Chocolate", quantity: 3, amount: new NumberDecimal("90") },
{ date: new ISODate("2019-01-28"), item: "Cake - Peanut Butter", quantity: 2, amount: new NumberDecimal("32") },
{ date: new ISODate("2019-01-30"), item: "Cake - Red Velvet", quantity: 1, amount: new NumberDecimal("20") },
{ date: new ISODate("2019-01-30"), item: "Cookies - Chocolate Chip", quantity: 6, amount: new NumberDecimal("24") },
{ date: new ISODate("2019-01-31"), item: "Pie - Key Lime", quantity: 2, amount: new NumberDecimal("40") },
{ date: new ISODate("2019-01-31"), item: "Pie - Banana Cream", quantity: 2, amount: new NumberDecimal("40") },
{ date: new ISODate("2019-02-01"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-02-01"), item: "Tarts - Apple", quantity: 2, amount: new NumberDecimal("8") },
{ date: new ISODate("2019-02-02"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-02-02"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2019-02-03"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") }
]);

updateMonthlySales(new ISODate("2019-01-01"));

6. capped collections​

capped collections: 固定大小的集合。

db.createCollection("log", {
capped: true,
size: 100000 // 最大大小为 100000 的集合 ( mongodb 会四舍五入到最接近 256 的整数倍)
});

db.createCollection(
"log2",
{
capped: true,
size: 5242880,
max: 5000 // 文档数最大为 5000
}
);

db.log.insertMany( [
{
message: "system start",
type: "startup",
time: 1711403508
},
{
message: "user login attempt",
type: "info",
time: 1711403907
},
{
message: "user login fail",
type: "warning",
time: 1711404209
},
{
message: "user login success",
type: "info",
time: 1711404367
},
{
message: "user logout",
type: "info",
time: 1711404555
}
] )

db.log.find();

db.log.find({
type: "info"
});

db.log.find().sort({
$natural: - 1 // 与插入的顺序相反
});

判断集合是否为capped collections

db.log.isCapped();

修改限制

db.runCommand( { collMod: "log", cappedSize: 5242880 } )
db.runCommand( { collMod: "log", cappedMax: 5000 } )

7. null 与 undefined​

MongoDB 8.0 之后,null 的比较不会匹配 undefined。


db.people.insertMany([
{
_id: 1,
name: null
},
{
_id: 2,
name: undefined
},
{
_id: 3,
name: ["Gabriel", undefined]
},
{
_id: 4,
names: ["Alice", "Charu"] // 注意,这里是 names ,不是 name
}
]);

db.people.find();


db.people.find({
name: null
}); // v8.0 之后,只匹配 null 和 name 不存在的数据. 即 id = 1 and id = 4


// 删除 name 字段中包含 undefined 的文档
db.people.updateMany(
{
name: {
$type: "undefined"
}
},
[{
$set: {
"name": {
$cond: {
// When "name" is an array, convert { name: [ "Alice", undefined ] }
// to { name: [ "Alice" ] }
if : {
$eq: [{
$type: "$name"
}, "array"]
},
then: {
$filter: {
input: "$name",
cond: {
$not: {
$eq: [{
$type: "$$this"
}, "undefined"]
}
}
},

},
// When "name" is scalar undefined, remove it
else : "$$REMOVE"
}
}
}
}]
);


db.people.updateMany( // 批量更新
{ },
[ {
$replaceWith: { // 集合更新管道,替换文档的根节点, 接受一个文档作为参数
$arrayToObject: { // $arrayToObject + $filter 生成不包含 undefined 字段的文档
$filter: {
input: { $objectToArray: "$$ROOT" }, // 讲文档的每个字段转为键值对的数组形式
cond: {
$not: { $eq: [ { $type: "$$this.v" }, "undefined" ] } // 检查 this.v 是否为 undefined
}
}
}
}
} ]
)

db.people.updateMany(
{ name: { $type: "undefined" } },
[ {
$set: {
"name": {
$cond: {
// When "name" is an array, convert { name: [ "Alice", undefined ] }
// to { name: [ "Alice", null ] }
if: {
$eq: [ { $type: "$name" }, "array" ]
},
then: {
$map: {
input: "$name",
in: {
$cond: {
if: { $eq: [ { $type: "$$this" }, "undefined" ] },
then: null,
else: "$$this"
}
}
},
},
// When "name" is the scalar undefined, convert to null
else: null
}
}
}
} ]
)

8. insert​

db.inventory.insertOne(
{
item: "canvas",
qty: 100,
tags: ["cotton"],
size: {
h: 28,
w: 35.5,
uom: "cm"
}
}
);

db.inventory.find();

db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], size: { h: 14, w: 21, uom: "cm" } },
{ item: "mat", qty: 85, tags: ["gray"], size: { h: 27.9, w: 35.5, uom: "cm" } },
{ item: "mousepad", qty: 25, tags: ["gel", "blue"], size: { h: 19, w: 22.85, uom: "cm" } }
])

db.inventory.insertMany([
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);

9. select​


// status = 'D'
db.inventory.find({
status: "D"
});

// status in ('A','D')
db.inventory.find({
status: {
$in: ["A", "D"]
}
});

// status = 'A' and qty < 30
db.inventory.find({
status: "A",
qty: {
$lt: 30
}
});

// status = 'A' or qty < 30
db.inventory.find({
$or: [{
status: "A"
}, {
qty: {
$lt: 30
}
}]
});

// status = 'A' and (qty < 30 or item like 'p%')
db.inventory.find({
status: "A",
$or: [{
qty: {
$lt: 30
}
}, {
item: /^p/
}]
})


// embedded document

db.inventory.insertMany( [
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);

db.inventory.find({
"size.uom": "in"
});

db.inventory.find({
"size.h": {
$lt: 15
}
});

// size.w = 21 and size.h = 14 and size.uom = 'cm'
db.inventory.find({
size: {
w: 21,
h: 14,
uom: "cm"
}
});


// arrays
db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], dim_cm: [ 14, 21 ] },
{ item: "notebook", qty: 50, tags: ["red", "blank"], dim_cm: [ 14, 21 ] },
{ item: "paper", qty: 100, tags: ["red", "blank", "plain"], dim_cm: [ 14, 21 ] },
{ item: "planner", qty: 75, tags: ["blank", "red"], dim_cm: [ 22.85, 30 ] },
{ item: "postcard", qty: 45, tags: ["blue"], dim_cm: [ 10, 15.25 ] }
]);

// tags = ["red","blank"]
db.inventory.find({
tags: ["red", "blank"]
});

// tags contains ["red","blank"]
db.inventory.find({
tags: {
$all: ["red", "blank"]
}
});

// 'red' in tags
db.inventory.find({
tags: "red"
});


// dim_cm 存在大于 25 的元素
db.inventory.find({
dim_cm: {
$gt: 25
}
});

// dim_cm 存在大于 15 小于 20 的元素
db.inventory.find({
dim_cm: {
$gt: 15,
$lt: 20
}
});

// dim_cm 的第 2 个元素大于 25
db.inventory.find({
"dim_cm.1": {
$gt: 25
}
});

// tags.length = 3
db.inventory.find({
"tags": {
$size: 3
}
});

// arrays of embeeded documents
db.inventory.insertMany( [
{ item: "journal", instock: [ { warehouse: "A", qty: 5 }, { warehouse: "C", qty: 15 } ] },
{ item: "notebook", instock: [ { warehouse: "C", qty: 5 } ] },
{ item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 15 } ] },
{ item: "planner", instock: [ { warehouse: "A", qty: 40 }, { warehouse: "B", qty: 5 } ] },
{ item: "postcard", instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }
]);

instock.warehouse = 'A' and instock.qty = 5 (需要顺序一致)

db.inventory.find({
"instock": {
warehouse: "A",
qty: 5
}
});

// instock.qty = 5 and instock.warehouse = 'A'
db.inventory.find({
"instock": {
qty: 5,
warehouse: "A"
}
});
// instock.qty <= 20
db.inventory.find({
'instock.qty': {
$lte: 20
}
});

// instock[0].qty <= 20
db.inventory.find({
'instock.0.qty': {
$lte: 20
}
});

// instock contains {qty: 5, warehouse: 'A'}
db.inventory.find({
"instock": {
$elemMatch: { // 不匹配顺序
qty: 5,
warehouse: "A"
}
}
});

// instock contains { 10 < qty <= 20 }
db.inventory.find({
"instock": {
$elemMatch: {
qty: {
$gt: 10,
$lte: 20
}
}
}
});

// instock.qty = 5 and instock.warehouse = 'A'
db.inventory.find({
"instock.qty": 5,
"instock.warehouse": "A"
})

指定返回字段

// project results 
db.inventory.insertMany( [
{ item: "journal", status: "A", size: { h: 14, w: 21, uom: "cm" }, instock: [ { warehouse: "A", qty: 5 } ] },
{ item: "notebook", status: "A", size: { h: 8.5, w: 11, uom: "in" }, instock: [ { warehouse: "C", qty: 5 } ] },
{ item: "paper", status: "D", size: { h: 8.5, w: 11, uom: "in" }, instock: [ { warehouse: "A", qty: 60 } ] },
{ item: "planner", status: "D", size: { h: 22.85, w: 30, uom: "cm" }, instock: [ { warehouse: "A", qty: 40 } ] },
{ item: "postcard", status: "A", size: { h: 10, w: 15.25, uom: "cm" }, instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }
]);

// status = 'A'
db.inventory.find( { status: "A" } )

// select _id,item , status from inventory where status = 'A'
db.inventory.find( { status: "A" }, { item: 1, status: 1 } );

// select item , status from inventory where status = 'A'
db.inventory.find( { status: "A" }, { item: 1, status: 1, _id: 0 } )

// 不显示 status instock 字段
db.inventory.find( { status: "A" }, { status: 0, instock: 0 } )

// 返回 item status size.uom 字段
db.inventory.find(
{
status: "A"
},
{
item: 1,
status: 1,
"size.uom": 1
}
)

db.inventory.find({
status: "A"
});

// 返回 item status instock.qty 字段
db.inventory.find({
status: "A"
}, {
item: 1,
status: 1,
"instock.qty": 1
});

分支条件

db.inventory.find(
{},
{
_id: 0,
item: 1,
status: {
$switch: { // 分支条件 $switch + branches + default 一起使用
branches: [
{
case: {
$eq: ["$status", "A"]
},
then: "Available"
},
{
case: {
$eq: ["$status", "D"]
},
then: "Discontinued"
},

],
default: "No status found"
}
},
area: { // $size.h * $size.w + " " + $size.uom
$concat: [
{
$toString: {
$multiply: ["$size.h", "$size.w"]
}
},
" ",
"$size.uom"
]
},
reportNumber: {
$literal: 1 // $literal 是一个特殊运算符,用于返回一个常量值(即字面值),它的作用是将提供的值直接作为结果输出,而不会被解释为字段或表达式。
}
}
);

10. $literal​

$literal 是一个特殊运算符,用于返回一个常量值(即字面值),它的作用是将提供的值直接作为结果输出,而不会被解释为字段或表达式。

db.inventory.aggregate([
{
$project:
{
aa: {
$literal: 1
}
}
}
]);

11. $exist​

查找不存在 item 的字段

db.inventory.find({
item: {
$exists: false
}
});

12. $currentDate​

将字段的值设置为当前日期。

db.customers.updateOne(
{ _id: 1 },
{
$currentDate: {
lastModified: true, // 将 lastModified 字段修改为当前时间
"cancellation.date": { $type: "timestamp" } // 将 cancellation.date 修改为当前时间戳
},
$set: {
"cancellation.reason": "user request", // 将 cancellation.reason 修改为 user request
status: "D" // 将 status 修改为 D
}
}
);

聚合方式

聚合变量需要使用 $$ ,并且使用双引号。eg: "$$NOW"
db.customers.updateOne(
{
_id: 1
},
[
{
$set: {
lastModified: "$$NOW", // 聚合变量需要使用 $$ ,并且使用双引号。
cancellation: {
date: "$$CLUSTER_TIME", // 时间戳
reason: "user request"
},
status: "D"
}
}
]
);

13. 更新文档​

  • updateOne: 修改单个文档
db.inventory.updateOne(
{ item: "paper" },
{
$set: { "size.uom": "cm", status: "P" },
$currentDate: { lastModified: true }
}
)
  • updateMany: 修改多个文档
db.inventory.updateMany(
{ "qty": { $lt: 50 } },
{
$set: { "size.uom": "in", status: "P" },
$currentDate: { lastModified: true }
}
)
  • replaceOne: 替换 _id 字段之外的整个文档内容。
db.inventory.replaceOne(
{ item: "paper" },
{ item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 40 } ] }
)

14. $set​

db.students.updateOne({
_id: 3
}, [{
$set: {
"test3": 98,
modified: "$$NOW"
}
}]);

15. $mergeObjects​

$mergeObjects 合并多个对象为一个对象,相同的字段以最后一个对象为准。

db.students2.updateMany({}, [
{
$replaceRoot: {
// 新增一个文档,因为第一个参数是 {}
newRoot:
{
$mergeObjects: [{ // $mergeObjects 合并多个对象为一个对象,相同的字段以最后一个对象为准。
quiz1: 0,
quiz2: 0,
test1: 0,
test2: 0
}, "$$ROOT"]
}
}
},
{
$set: {
modified: "$$NOW"
}
}
]);

16. $replaceRoot​

用于替换文档的根内容,即完全替换当前文档的顶层结构为一个新的结构。

17. $trunc​

$trunc 是一个算术运算符,用于截断数字的小数部分,保留指定位数小数。不会四舍五入


db.students3.updateMany(
{}, // 会匹配到所有字段
[
{
$set: {
average: { // 新增一个 average 字段
$trunc: [{ // 截断小数,保留指定位数,此处保留 0 位小数
$avg: "$tests"
}, 0]
},
modified: "$$NOW"
}
},
{
$set: {
grade: {
$switch: {
branches: [
{
case: { // >= 90 A
$gte: ["$average", 90]
},
then: "A"
},
{
case: { // >= 80 B
$gte: ["$average", 80]
},
then: "B"
},
{
case: { // >= 70 C
$gte: ["$average", 70]
},
then: "C"
},
{
case: { // >= 60 D
$gte: ["$average", 60]
},
then: "D"
}
],
default: "F"
}
}
}
}
]
)

18. $concatArrays​

$concatArrays 是一个用于将两个或多个数组连接在一起的操作符。

db.students4.updateOne({
_id: 2
}, [{
$set: {
quizzes: {
$concatArrays: ["$quizzes", [8, 6]]
}
}
}]);

19. $map​

{ $map: { input: <expression>, as: <string>, in: <expression> } }

20. 删除文档​


db.inventory.find();
db.inventory.insertMany( [
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "P" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" },
] );
db.inventory.deleteMany({}); // 删除集合中所有文档
db.inventory.deleteMany({ status : "A" }) // 删除 status = 'A'
db.inventory.deleteOne( { status: "D" } ) // 删除 status ='D' 的第一个文档

// 1. 不会丢弃索引
// 2. 单个文档的操作是原子性的
// 3. write concern

21. Write Concern 写入确认级别​

写操作的确认策略和数据持久化级别的一组配置。

w : 定义需要确认写入的节点数量或策略。

  • 开发和测试环境 w:0 或 w:1
  • 一般业务系统: w: majority 或 w: 2~3
  • 金融系统: w: majority and j: true 会显著的降低写操作的性能,需要权衡写入速度和可靠性。

j:是否等待数据持久化到磁盘 (journal 文件)

wtimeoutMS:等待写操作确认的超时时间。

db.collection.insertOne(
{ name: "Alice", age: 25 },
{ writeConcern: { w: "majority", j: true } }
);

majority

  1. 写入数据必须被直接点持久化到内存中。
  2. 被副本集中大多数节点确认完成 (N/2 + 1)
    • 副本集有 3 个节点, majority = 2
    • 副本集有 5 个节点, majority = 3

22. Bulk Write​

try {
db.pizzas.bulkWrite( [
{ insertOne: { document: { _id: 3, type: "beef", size: "medium", price: 6 } } },
{ insertOne: { document: { _id: 4, type: "sausage", size: "large", price: 10 } } },
{ updateOne: {
filter: { type: "cheese" },
update: { $set: { price: 8 } }
} },
{ deleteOne: { filter: { type: "pepperoni"} } },
{ replaceOne: {
filter: { type: "vegan" },
replacement: { type: "tofu", size: "small", price: 4 }
} }
] )
} catch( error ) {
print( error )
}

Ordered,默认 true

有序:其中一个写操作失败,剩余的写操作不会执行。
无序:其中一个写操作时发生错误,MongoDB 会继续处理列表中剩余的写操作。

db.collection.bulkWrite(
[
{ insertOne : <document> },
{ updateOne : <document> },
{ updateMany : <document> },
{ replaceOne : <document> },
{ deleteOne : <document> },
{ deleteMany : <document> }
],
{ ordered : false } // 默认有序。
)

分片集合的批量插入

  • 预分割集合 Pre-Split the Collection
  • 无序写入到 mongos Unordered Writes to mongos
  • 避免单调 Avoid Monotonic Throttling

23. Retryable Writes 可重试写入​

mongosh --retryWrites=true

Prerequisites:

  • 需要副本集或分片集群,不支持独立实例。
  • 需要支持文档级锁定的存储引擎,例如 WiredTiger 或内存存储引擎。
  • MongoDB 3.6+
  • Write Concern 的 w 不为 0 , 0 表示不重试

24. Retryable Reads 可重试读取​

MongoDB Server 4.2+

25. Geospatial Queries 地理空间搜索​

GeoJSON 对象。

<field>: { type: <GeoJSON type> , coordinates: <coordinates> }

先 longitude 经度(-180,180) ,后 latitude 维度(-90,90)。

location: {
type: "Point",
coordinates: [-73.856077, 40.848447]
}

Legacy Coordinate Paris 传统坐标对

  • 数组指定:<field>: [ <x>, <y> ]
  • 文档嵌入:<field>: { <field1>: <x>, <field2>: <y> }

26. Read Concern​

readConcern 选项,可以控制从 replica sets 和 sharded clusters 读取的数据的 一致性 consistency 和 隔离性 isolation。

在执行 find 或者 aggregate 时,可以通过 readConcern 来把控返回数据的准确性和可靠性。

Read Concern Levels 读关注级别​

read concern 决定了查询返回的数据的一致性级别。它指定了查询应返回的数据版本。

MongoDB 支持的 Read Concern 级别

  • local(default)
  • available
  • majority
  • linearizable
  • snapshot

causally consistent sessions 因果一致性会话

Causal Consistency 是一种保证,指在启用因果一致性的情况下,读操作会确保在逻辑上尊重因果顺序。

在发生相关的操作时,Causal Consistency 能确保:如果有依赖关系,读操作不会早于依赖的写操作完成。

MongoDB 默认不开启因果一致性,但可以通过 API 显式启用。

eg: 如果 A 写入了数据,随后 B 读取了数据,在因果一致性中,B 应该总是能看到 A 的写操作结果,而不会读到那个写入之前的快照。

local​

readConcern 的默认级别。

local 仅从主节点读取本地数据,这些数据已经过一些基本的同步,且不会读取到孤立文档。

local : 返回实例中的数据,不保证返回的数据被写入大多数副本集成员。(返回的数据可能会被回滚)

使用前提(任何情况)

  1. 单独使用
  2. 开启了因果一致性会话 causally consistent session
  3. 开启了事务 transactions
available​

available 适用于对实时场景非常高的情况。

使用前提:

  1. 没有开启 causally consistent session 和 transactions 的会话。 (即无法在 causally consistent session 和 transactions 的会话中使用)
  2. 仅适用于 sharded clusters

对于 sharded clusters , available 提供了最低延迟的读取。

  • 不需要等下多少票确认
  • 直接从当前在线节点返回数据
  • 读取逻辑简单,无需进行额外的状态检查或锁定

majority​

对于与多文档事务无关的读取操作, majority 读取的数据已被副本集的大多数成员确认。(读取的文档是持久的,并保证不会被回滚)

CURD Concepts (CURD 概念)​

原子性和事务​

https://www.mongodb.com/docs/manual/core/write-operations-atomicity/

MongoDB 在单个文档上的修改是原子性的。

当单个操作修改多个文档时,每个文档的修改是原子的,但是整个操作不是原子的。(操作可能会交错进行)

如果需要原子性读取/写入多个文档时,MongoDB z支持分布式事务。(distributed transactional)

分布式查询​

Replica Sets

默认情况下,Client 从 Replica Sets 的 primary 节点 读取数据。

但是 Client 可以通过指定 read preference (读取偏好)将读取请求导向到其它节点。

在 replica sets 中,所有的写操作都会发送到 primary 节点中。 primary 节点应用写操作并将操作记录在 primary 节点的操作日志或 oplog 中。

sharded clusters

Causal Consistency 因果一致性​

如果一个操作在逻辑上依赖于前一个操作,那么这些操作之间存在因果关系。

Query Optimization 查询优化​

索引通过减少查询操作需要处理的数据量来提高读取操作的效率。

创建索引​

// 在 type 上查询
db.inventory.find( { type: typeValue } );

// 在 type 字段上创建索引 , 1 表示升序
db.inventory.createIndex( { type: 1 } )

Query Selectivity 查询选择性​

查询条件能够有效地从集合中排除或过滤掉文档。

Covered Query 覆盖查询​

覆盖查询是一种可以完全使用索引来满足的查询,而不需要检查任何文档。

  • 查询中的字段都是索引的一部分
  • 结果中返回的所有字段都在同一个索引中
  • 查询中没有字段等于 null. 例如 {"field":null} {"field":{ $eq : null }}

案例:

db.inventory.createIndex( { type: 1, item: 1 } )

db.inventory.find(
{ type: "food", item:/^c/ },
{ item: 1, _id: 0 } // 为了使用索引覆盖查询,必须指定 _id: 0 以从结果中排查 _id 字段(因为索引不包含 _id 字段)。
)

Embedded Documents

索引可以覆盖对嵌入文档中字段的查询。

db.userdata.createIndex(
{ "user.login": 1 }
)

db.userdata.find(
{ "user.login": "tester" },
{ "user.login": 1, _id: 0 }
)

慢查询​

查询 mongodb 的性能

  • $currentOp 查询有关活动操作和游标的信息
  • top 获取额外的操作计数和延迟统计信息
  • serverStatus 查询执行的性能问题和异常
  • $queryStats 常见查询分片的信息,提供了运行的查询类型的整体视图。
  • $indexStats 查询索引统计信息

慢查询分析

  • system.profile , 启用后,数据库分析器会将有关慢查询的信息存储在 system.profile 集合中。
  • db.collection.explain() 查询计划和执行统计信息。

执行高级查询分析

  • $planCacheStats 返回有关集合计划缓存信息。 查询计划器(query planner) 使用这些计划来高效的完成查询。

查询计划 query plan​

  • db.collection.explain().find()
  • db.collection.find().explain() Explain plan results
  • 查询所需时间
  • 是否使用了索引
  • 扫描的文档和索引的数量

verbosity mode​

  1. queryPlanner
  • 默认值
  • 返回了 winning plan 的信息
  • 不会实际执行查询,只是显示查询计划
  1. executionStats
  • 执行查询并提供 winning plan 的详细执行统计信息(不会显示被拒绝的计划的执行统计)
    • 查询计划
    • 执行时间
    • 文档扫描数量
    • 索引条目扫描数量
    • 返回文档数
    • 内存使用情况
  1. allPlansExecution
  • 不仅显示 winning plan ,还会显示所有被考虑的计划。
  • 实际执行查询,并提供每个计划的执行统计信息。
  • 显示了更多执行细节
    • 所有被评估的计划
    • 每个计划执行时间
    • 每个阶段处理的文档数量
    • 内存使用情况

结果分析​

具体字段含义见官网:https://www.mongodb.com/docs/manual/reference/explain-results/

  1. queryPlanner.winningPlan.stage
  • COLLSCAN 表示集合扫描,必须逐文档扫描整个集合(全文扫描)
  • IXSCAN 表示使用了索引。
  • FETCH 检索文档
  • GROUP 分组文档
  • SHARD_MERGE 合并来自分片的结果
  • SHARDING_FILTER 过滤掉来自分片的孤立文档
  • TS_MODIFY 修改时间序列集合
  • BATCHED_DELETE 用于内部批处理的多个文档删除
  • EXPRESS 适用于一组有限的查询
  1. executionStats.nReturned 返回文档的总数

  2. executionStats.totalKeysExamined 扫描到的索引条目(index entries),0 表示未使用索引。

  3. executionStats.totalDocsExamined 扫描的文档总数。