MongoDB基础
qinsz 5/18/2024 MongoDB
# 一 、基础
# 1、存储结构
MongoDB 的存储结构只是名称上区别于传统的关系型数据库,实际上类似mysql的结构化存储。主要由如下三个单元组成:
- 文档(Document) :MongoDB 中最基本的单元,由 BSON 键值对(key-value)组成,类似于关系型数据库中的行(Row)。
- 集合(Collection) :一个集合可以包含多个文档,类似于关系型数据库中的表(Table)。
- 数据库(Database) :一个数据库中可以包含多个集合,可以在 MongoDB 中创建多个数据库,类似于关系型数据库中的数据库(Database)。
也就是说,MongoDB 将数据记录存储为文档 (BSON 文档),这些文档在集合中聚集在一起,数据库中存储一个或多个文档集合。
# 2、与关系型数据中的术语对比
| xSQL | MongoDB |
|---|---|
| 表(Table) | 集合(Collection) |
| 行(Row) | 文档(Document) |
| 列(Col) | 字段(Field) |
| 主键(Primary Key) | 对象 ID(ObjectId) |
| 索引(Index) | 索引(Index) |
| 数组(Array) | 数组(Array) |
# 3、文档(Document)
- MongoDB中的记录就是一个BSON文档,它存在与一个集合中,类比于关系型数据库中表内的一条记录。
- 它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。
- 字段的值可能包括其他文档、数组和文档数组。
{
"name": "John",
"age": 20,
"permissions": ["read", "write"]
}
1
2
3
4
5
2
3
4
5
# 4、集合(Collection)
- MongoDB集合存在于数据库中,类比于关系型数据中的表(table)
- 但MongoDB的集合没有固定的结构,不需要像关系型数据那样需要提前创建column,这意味着同一集合中可以插入完全不同格式和类型的文档(即数据记录)。
{
"name": "John",
"age": 20,
"permissions": ["read", "write"]
}
1
2
3
4
5
2
3
4
5
{
"name": "Petter",
"role": "student"
}
1
2
3
4
2
3
4
上面两条记录可以插入到同一个集合中
需要注意的是,虽然结构的灵活性带来了很多便利,但也需要考虑一些潜在的问题:
- 性能问题:如果一个集合中的文档结构差异极大,可能会对查询性能产生影响。
- 维护复杂性:随着时间的推移,如果没有良好的数据治理,集合中文档的结构变化可能导致数据管理变得复杂。
- 应用逻辑复杂度:应用层需要能够处理结构不一的数据,这可能增加编码的复杂性
因此,在设计数据库和应用程序时,合理规划文档结构的一致性和变化是非常重要的。在保持数据灵活性和易于维护之间找到平衡点,是高效使用 MongoDB 的关键。
# 二、基本使用
通过在mongo指令进入与MongoDB的交互模式。以下指令均在交互模式下使用
mongo
1
# 1、创建数据库和集合
//切换当前访问的数据库
// 如果不存在将自动创建,不需要像关系型数据库那样执行create操作
use mydatabase
// 创建一个名为 'users' 的集合
db.createCollection("users")
1
2
3
4
5
6
2
3
4
5
6
# 2、 插入数据
db.users.insertOne({name: "John", age: 20})
1
# 3、查询数据
db.users.find({}) // 查询集合中的所有文档
db.users.find({name: "John"}) // 查询所有名为 "John" 的文档
1
2
2
# 4、更新数据
// 将name为John的文档的age字段更新为21。仅修改一个文档。
db.users.updateOne({name: "John"}, {$set: {age: 21}})
1
2
2
# 5、删除数据
// 删除name为John的文档。仅删除一个文档。
db.users.deleteOne({name: "John"})
1
2
2
# 三、更多CRUD语法
# 1、插入
- 插入单个文档:
db.users.insertOne({
name: "John",
age: 30,
email: "john@example.com"
})
1
2
3
4
5
2
3
4
5
- 插入多个文档:
db.collection('users').insertMany([
{ name: "Alice", age: 25, email: "alice@example.com" },
{ name: "Bob", age: 27, email: "bob@example.com" }
])
1
2
3
4
2
3
4
# 2、查询
- 查询所有:
db.users.find({})
1
- 查询单个:
db.users.findOne({ name: "John" })
1
- 条件查询:
// 查找所有年龄大于 25 的文档
db.users.find({ age: { $gt: 25 } })
1
2
2
# 3、更新
- 更新单个:
db.users.updateOne(
{ name: "John" }, // 查询条件
{ $set: { age: 21 } } // 更新操作
)
1
2
3
4
2
3
4
- 更新多个:
db.users.updateMany(
{ age: { $gt: 25 } }, // 查询条件
{ $set: { isActive: true } } // 更新操作
)
1
2
3
4
2
3
4
- 替换一个文档:
db.users.replaceOne(
{ name: "Alice" },// 旧文档条件
{
name: "Alice",
age: 26,
email: "alice@example.com",
isActive: true
} // 新文档完全替换旧文档
)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4、删除
- 删除单个:
db.users.deleteOne({ name: "John Doe" })
1
- 删除多个:
db.users.deleteMany({ isActive: true })
1
# 四、更多查询
# 1、多个字段作为条件
// 查询名字为 "John" 且年龄为 30 的用户
db.users.find({
name: "John",
age: 30
})
1
2
3
4
5
2
3
4
5
# 2、使用$and运算符
// 查询名字为 "John" 且年龄大于 25 但小于 35 的用户
db.users.find({
$and: [
{ name: "John" },
{ age: { $gt: 25 } },
{ age: { $lt: 35 } }
]
})
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 3、使用$or运算符
// 查询名字为 "John" 或年龄大于30的用户
db.users.find({
$or: [
{ name: "John" },
{ age: { $gt: 30 } }
]
})
1
2
3
4
5
6
7
2
3
4
5
6
7
# 4、使用$not和$nor运算符
$not用于反转一个条件的结果,而$nor可以反转多个条件的结果,即查找不匹配所有给定条件的文档。
// 查询名字不是 "John" 的所有用户
db.users.find({
name: { $not: { $eq: "John" } }
})
// 查询既不是 "John" 也不大于 30 岁的用户
db.users.find({
$nor: [
{ name: "John" },
{ age: { $gt: 30 } }
]
})
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 5、复合条件
将这些运算符组合使用,创建更为复杂的查询条件
// 查询名字为 "John" 或 "Alice" 且年龄大于 25 的用户
db.users.find({
$or: [
{ name: "John" },
{ name: "Alice" }
],
age: { $gt: 25 }
})
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 五、索引
与关系型数据库中的索引一样,索引是一种用于提升数据库查询效率的数据结构。
# 1、单字段索引
假如name和email是常用的查询条件,那么可以为它们创建索引。
db.users.createIndex({ name: 1 });
db.users.createIndex({ email: -1 });
1
2
2
1表示创建生序索引,-1表示逆序索引。- 上述例子中,MongoDB会以字母顺序(A到Z)来存储name的索引。
- 在单字段索引中,索引方向对查询效率几乎没有影响,因为MongoDB能够以任意方向进行扫描检索。
# 2、复合索引
- 条件同时包含复合索引中的多个字段时,复合索引可以提供比单独索引更佳的性能
- 复合索引可以优化多字段查询,但它也会占用更多的磁盘空间,并可能导致写入操作的性能稍微降低,因为更新任何一个索引字段都需要更新整个复合索引
- 适用于查询模式固定,经常需要按照相同的字段组合进行查询和排序的场景
db.users.createIndex({ name: 1, email: 1 });
1
// 创建唯一索引
db.users.createIndex({ username: 1, email: 1 }, { unique: true })
1
2
2
# 3、部分索引
可以通过设置过滤条件,只为复合条件的部分文档创建索引:
- 空间效率:只索引符合特定条件的文档,可以显著减少索引所占用的存储空间
- 性能提升:查询时,因为索引包含的数据量更小,查询速度可能更快。
- 适用性:非常适用于数据集中只有部分文档包含查询字段的场景。
db.users.createIndex(
{ location: 1 },
{ partialFilterExpression: { location: { $exists: true } } }
)
1
2
3
4
2
3
4
{ location: 1 }:这表示在 users 集合上为 location 字段创建一个升序索引{ partialFilterExpression: { location: { $exists: true } } }: 指定了索引创建的过滤条件,只有当文档中的 location 字段存在时,这个文档才会被包含在索引中
# 4、稀疏索引
仅对那些包含被索引字段的文档进行索引。
db.users.createIndex({ location: 1 }, { sparse: true })
1
- 只有文档中含有location字段的才加入索引
- 优点与作用
部分索引类似 稀疏索引使用更简单,但部分索引可以设置更复杂的过滤场景
# 5、查看已经创建的索引
db.users.getIndexes()
1
如果仅为name字段创建过单字段索引,可以看到下面的输出内容
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"name" : 1
},
"name" : "name_1"
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 显示有两条索引:
- _id:mongodb为每个文档自动生成的主键
- name:我们自定义的字段名
- v: 这代表索引的版本
- key: 描述索引的键(字段)和方向
- name: 自动生成的默认索引名称,通常是基于索引字段和方向组合而成的
# 6、删除索引
- 通过索引名删除
db.users.dropIndex("name_1")
1
- 通过索引键删除
db.users.dropIndex({ "name": 1 })
1
# 7、监控索引效果
创建索引后,可通过explain()语句来查看查询的执行效果
db.users.find({ name: "John" }).explain("executionStats")
1