MongoDB 基础

一、数据类型

1.1 BSON

文档 (Documents) 是 MongoDB 中数据的基本单元,它是键值对的有序集合,在数据结构上类似于 JSON,但是由于 JSON 只支持基本的 6 种数据类型:null,布尔,数字,字符串,数组和对象,因此其表达能力有限,所以 MongoDB 基于 JSON 拓展出新的数据格式 BSON 。BSON (Binary JSON) 是类似 JSON 文档的二进制编码序列化格式,与 JSON 类似,但支持更多的数据类型。当前 MongoDB 4.0 的 BSON 支持以下所有的数据类型:

Type (类型) Alias (别名) Notes (注释)
Double “double” 双精度型
String “string” 字符串类型,默认的编码类型为 UTF-8
Object “object” 对象类型
Array “array” 数据类型
Binary data “binData” 二进制数据
Undefined “undefined” 该数据类型已经废弃
ObjectId “objectId” 对象 id
Boolean “bool” 布尔值
Date “date” 日期类型
Null “null”
Regular Expression “regex” 正则表达式
DBPointer “dbPointer” 该数据类型已经废弃
JavaScript “JavaScript”
Symbol “symbol” 该数据类型已经废弃
JavaScript (with scope) “javascriptWithScope”
32-bit integer “int” 32 位整型
Timestamp “timestamp” 时间戳类型
64-bit integer “long” 64 位整型
Decimal128 “decimal” 3.4 版本新增的数据类型,类似 Java 中的 BigDecimal 类型,
用于解决浮点型丢失精度的问题
Min key “minKey”
Max key “maxKey”

1.2 ObjectId

在上面的表格中有一个比较重要的数据类型是:ObjectId 。存储在 MongoDB 集合中的每个文档都有一个唯一的 _id 字段作为主键,可以在插入的时候手动指定,或者由程序自动生成,这个字段可以是任何的数据类型,默认是 ObjectId 类型。ObjectId 使用 12 字节的存储空间,是一个由 24 个十六进制数字组成的字符串,每个字符串可以存储两个十六进制数字:

  • 前 4 个字节是从标准纪元开始的时间戳,单位为秒;
  • 中 5 个字节是由两个部分组成:前 3 个字节是所在主机的唯一标识,通常是主机名的散列值,用于保证不同主机生成不同 ObjectId ,后两个字节是进程标识符 (PID) ,用于保证同一主机上不同进程产生不同的 ObjectId。
  • 最后 3 个字节是一个自动增加的计数器,确保相同主机上相同进程在同一秒产生的 ObjectId 也是不同的,即每个进程一秒最多可以拥有 16777216 个不同的 ObjectId (16777216 = (2**8)**3,1 个字节等于 8 位二进制) 。

二、新增数据

db.collection.insert()

MongoDB 3.2 之前,插入数据的语法如上,可以用于插入单条或者多条数据。在 3.2 之后,MongoDB 为了增强 API 的语义,增加了如下两个 API ,分别用于显示表达插入单条数据和多条数据的行为:

db.collection.insertOne() 
db.collection.insertMany() 

新增单条数据的示例如下。额外需要说明的是,在插入文档前,文档所属的集合不必预先创建,程序会自动创建:

db.user.insertOne({
    name: "heibai",
    age: 26,
    birthday: new Date(1998,08,23),
    createTime: new Timestamp(),
    Hobby: ["basketball", "football", "tennis"]
})

新增多条数据的示例如下:

db.user.insertMany([
    {
        name: "hei",
        age: 32,
        birthday: new Date(1989,08,23),
        createTime: new Timestamp(),
        Hobby: ["basketball", "football", "tennis"]
    },
    {
        name: "ying",
        age: 46,
        birthday: new Date(1978,08,23),
        createTime: new Timestamp(),
        Hobby: ["basketball", "football", "tennis"]
    }
])
# 此时会返回新插入的数据的ObjectId
{
    "acknowledged" : true,
    "insertedIds" : [
        ObjectId("5d3d0489ad38cd3becc7b03b"),
        ObjectId("5d3d0489ad38cd3becc7b03c")
    ]
}

三、查询数据

3.1 基本 API

查询数据的基本语法如下:

db.collection.find(<query>, <projection>)
  • <query>:用于指定查询条件,不加任何条件则默认查询集合中全部数据;
  • <projection>:可选操作,用于过滤返回字段,1 表示对应的字段包含在返回结果中,0 表示不包含,示例如下:
db.user.find({},{name: 1, ObjectId:-1})}

3.2 等值查询

想要查询某个字段等于指定值的数据,可以使用如下语法:

db.user.find({name:"heibai"})

上面的语法实际上是 $eq 操作的简写形式,如下:

db.user.find({name: {$eq: "heibai"}})

所以如果你想要进行非等值查询,则可以使用 $ne 操作符,代表 not equal ,示例如下:

db.user.find({name: {$ne: "heibai"}})

特别的,如果你想允许某个字段等于多个值,可以使用 $in 操作符,示例如下:

db.user.find({name: {$in:["heibai","ying"]} })

3.3 范围查询

MongoDB 提供了比较操作符 $lt$lte$gt$gte ,分别对应 和 >= ,主要用于范围查查询,示例如下:

db.user.find({age: {$gt: 20, $lt: 40}})

3.4 逻辑查询

MongoDB 提供了逻辑操作符 $or$and$not$nor ,用于处理多个条件间的逻辑关系,示例如下:

查询姓名为 heibai 或者年龄大于 30 岁的所有用户,此时可以使用 $or 操作符:

db.user.find( { $or: [{ name: "heibai" }, { age: { $gt: 30 } }] })

查询姓名不是以 hei 开头的所有用户,此时可以使用 $not 操作符来配合正则表达式:

db.user.find({name: {$not: /^hei*/}})

如果要求 name 字段的值不能为 heibai, age 字段的值不能大于 30,则对应的查询语句如下:

db.user.find( { $nor: [{ name: "heibai" }, { age: { $gt: 30 } }] })

$and 操作符的使用率比较低,因为此时更好的方式是把多个条件写到同一个对象中,从而减少额外嵌套。

3.5 集合查询

如果需要查询个人爱好中有 football 的所有用户,即集合 Hobby 中存在 football 即可,对应的查询语句如下:

db.user.find({Hobby: "football"})

如果想要获取集合中指定位置等于指定值的文档,对应的查询语句如下:

db.user.find({"Hobby.2": "football"})

如果想要约束集合必须包含多个指定值,此时可以使用 $all 操作符:

db.user.find({Hobby:{ $all: ["football", "tennis"]}})

查询时如果只想返回集合的部分内容,则可以使用 \(slice ,\)slice 接收一个参数 n,正数表示获取集合的前 n 个参数,负数表示获取集合末尾的 n 个参数,示例如下:

db.user.find({name: "heibai"},{Hobby:{$slice: 2}})

3.6 额外操作

在查询操作之后,MongoDB 还提供了三个额外的函数 skipsortlimit ,分别用于表示跳过一定量的数据、按照指定规则对数据进行排序和限制返回数据的量,示例如下:

db.user.find({})
   .skip(1)
   .sort({_id:-1})
   .limit(10)

四、修改数据

4.1 基本 API

MongoDB 提供了以下三个 API,分别用于修改单条数据、多条数据和执行单条数据替换:

db.collection.replaceOne(<filter>, <update>, <options>)
db.collection.updateOne(<filter>, <update>, <options>)
db.collection.updateMany(<filter>, <update>, <options>)
  • <filter>:过滤条件,用于查询需要修改的数据;
  • <update>:更改操作或新文档数据;
  • <options>:可选操作,常用的可选操作是 upsert ,当其为 true 时,代表如果按照过滤条件没有找到对应的文档,则将待更改的数据插入到集合中;当其为 false 时,如果没有找到数据,则不执行任何操作。示例如下:
db.user.replaceOne(
    { _id: ObjectId("5d3d00a4ad383d3becc7b03a")},
    {
        name: "danrenying",
        age: 32,
        birthday: new Date(1995,08,23),
        createTime: new Timestamp(),
        Hobby: ["basketball", "football", "tennis"]
    },
    {upsert : true}
)

4.2 常规修改器

想要对数据进行修改,必须明确表达修改行为,在 MongoDB 中这是通过修改器来实现的,常用的修改器如下:

1. $set

用于修改具体的字段,如果待修改的字段不存在,则会新增该字段。示例如下:

db.user.updateOne(
    { name: "danrenying"},
    { $set: {age: 66} }
)

2. $inc

用于对指定字段的值进行增加或减少,示例如下:

db.user.updateOne(
    { name: "danrenying"},
    { $inc: {age: -10} }
)

4.3 数组修改器

在修改操作中,比较复杂的是对数组数据的修改,为了解决这个问题,MongoDB 提供了一系列的数组修改器,用于数组操作:

1. $push

用于往数组中新增数据,示例如下。使用 $each 可以一次添加多个元素:

db.user.updateOne(
    { name: "danrenying"},
    { $push: {"Hobby": {$each: ["film","music"]}} }
)

2. $addToSet

该修改器可以把数组当做集 (set) 来使用,即只能添加当前数组中不存在的数据,示例如下:

db.user.updateOne(
    { name: "danrenying"},
    { $addToSet: {"Hobby": {$each: ["film","music"]}} }
)

3. $pop

该修改器可以从数组任意一端删除元素,-1 代表从数组头删除元素,1 代表从数组尾删除元素,示例如下:

db.user.updateOne(
    { name: "danrenying"},
    { $pop: {"Hobby": -1} }
)

4. index

用于直接修改指定下标位置的元素,示例如下:

db.user.updateOne(
    { name: "danrenying"},
    { $set: {"Hobby.0": "Cooking"} }
)

五、删除数据

MongoDB 提供了以下两个 API 用于删除操作:

db.collection.deleteMany()
db.collection.deleteOne()

使用示例如下:

db.user.deleteOne(
    { name: "danrenying"}
)

参考资料

  • 官方文档:MongoDB CRUD Operations
  • Kristina Chodorow . MongoDB权威指南(第2版). 人民邮件出版社 . 2014-01
下一节:单字段索引、复合索引、多键索引、哈希所有、地理空间索引、文本索引;唯一索引、稀疏索引、部分索引、TTL 索引