MongoDB基础

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

# 4、集合(Collection)

  • MongoDB集合存在于数据库中,类比于关系型数据中的表(table)
  • 但MongoDB的集合没有固定的结构,不需要像关系型数据那样需要提前创建column,这意味着同一集合中可以插入完全不同格式和类型的文档(即数据记录)。
{
  "name": "John",
  "age": 20,
  "permissions": ["read", "write"]
}
1
2
3
4
5
{
  "name": "Petter",
  "role": "student"
}
1
2
3
4

上面两条记录可以插入到同一个集合中
需要注意的是,虽然结构的灵活性带来了很多便利,但也需要考虑一些潜在的问题:

  • 性能问题:如果一个集合中的文档结构差异极大,可能会对查询性能产生影响。
  • 维护复杂性:随着时间的推移,如果没有良好的数据治理,集合中文档的结构变化可能导致数据管理变得复杂。
  • 应用逻辑复杂度:应用层需要能够处理结构不一的数据,这可能增加编码的复杂性

因此,在设计数据库和应用程序时,合理规划文档结构的一致性和变化是非常重要的。在保持数据灵活性和易于维护之间找到平衡点,是高效使用 MongoDB 的关键。

# 二、基本使用

通过在mongo指令进入与MongoDB的交互模式。以下指令均在交互模式下使用

mongo
1

# 1、创建数据库和集合

//切换当前访问的数据库
// 如果不存在将自动创建,不需要像关系型数据库那样执行create操作
use mydatabase

// 创建一个名为 'users' 的集合
db.createCollection("users")  
1
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

# 4、更新数据

// 将name为John的文档的age字段更新为21。仅修改一个文档。
db.users.updateOne({name: "John"}, {$set: {age: 21}})
1
2

# 5、删除数据

// 删除name为John的文档。仅删除一个文档。
db.users.deleteOne({name: "John"})
1
2

# 三、更多CRUD语法

# 1、插入

  • 插入单个文档:
db.users.insertOne({
    name: "John",
    age: 30,
    email: "john@example.com"
})
1
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、查询

  • 查询所有:
db.users.find({})
1
  • 查询单个:
db.users.findOne({ name: "John" })
1
  • 条件查询:
// 查找所有年龄大于 25 的文档
db.users.find({ age: { $gt: 25 } })
1
2

# 3、更新

  • 更新单个:
db.users.updateOne(
    { name: "John" },        // 查询条件
    { $set: { age: 21 } }    // 更新操作
)
1
2
3
4
  • 更新多个:
db.users.updateMany(
    { age: { $gt: 25 } },        // 查询条件
    { $set: { isActive: true } } // 更新操作
)
1
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

# 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、使用$and运算符

// 查询名字为 "John" 且年龄大于 25 但小于 35 的用户
db.users.find({
    $and: [
        { name: "John" },
        { age: { $gt: 25 } },
        { age: { $lt: 35 } }
    ]
})
1
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

# 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

# 5、复合条件

将这些运算符组合使用,创建更为复杂的查询条件

// 查询名字为 "John" 或 "Alice" 且年龄大于 25 的用户
db.users.find({
    $or: [
        { name: "John" },
        { name: "Alice" }
    ],
    age: { $gt: 25 }
})
1
2
3
4
5
6
7
8

# 五、索引

与关系型数据库中的索引一样,索引是一种用于提升数据库查询效率的数据结构。

# 1、单字段索引

假如name和email是常用的查询条件,那么可以为它们创建索引。

db.users.createIndex({ name: 1 });
db.users.createIndex({ email: -1 });
1
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

# 3、部分索引

可以通过设置过滤条件,只为复合条件的部分文档创建索引:

  • 空间效率:只索引符合特定条件的文档,可以显著减少索引所占用的存储空间
  • 性能提升:查询时,因为索引包含的数据量更小,查询速度可能更快。
  • 适用性:非常适用于数据集中只有部分文档包含查询字段的场景。
db.users.createIndex(
    { location: 1 },
    { partialFilterExpression: { location: { $exists: true } } }
)
1
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
  • 显示有两条索引:
    • _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
Last Updated: 5/22/2024, 12:12:28 PM