meface/docs/article/db/mongodb_base.md

1285 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: MongoDB基础
date: 2020-12-29
author: ac
tags:
- MongoDB
categories:
- Database
---
## 一、简介
`MongoDB`是一个开源、高性能、无模式(没有确定的列)的文档型数据库,设计之初是为了简化开发和方便扩展,是`NoSQL`数据库产品中的一种。是最像关系型数据库的非关系型数据库。
它支持的数据库结构非常松散,是一种类似`JSON`格式的`BSON`,所以它既可以存储比较复杂的数据类型,又相当的灵活。
`MongoDB`中的记录是一个文档,它是一个由记录和键值对(`filed`:`value`)组成的数据结构。`MongoDB`文档类似于`JSON`对象,即一个文档可以认为就是一个对象。字段的数据类型是字符型,它的值除了使用一些类型外,还可以包括其他文档、普通数组和文档数组。
### 体系结构
**关系型数据库与`MongoDB`的对比**
| SQL术语/概念 | MongoDB | 描述 |
| ------------ | ----------- | ---------------------------------------- |
| database | database | 数据库 |
| table | collection | 数据库表/集合 |
| row | document | 数据记录行/文档 |
| column | field | 数据字段/域 |
| index | index | 索引 |
| table joins | | 表连接,`MongoDB`不支持 |
| | 嵌入文档 | `MongoDB`通过嵌入式文档来替代多表连接 |
| primary key | primary key | 主键,`MongoDB`自动将`_id`字段设置为主键 |
### 数据模型
`MongoDB`的最小存储单位是文档document对象。
文档对象对应关系型数据库中的行。数据在`MongoDB`中是以`BSON`(Binary-JSON)文档的格式存储在磁盘上的。
`BSON`(Binary Serizlized Document Format)是一种类`JSON`的二进制形式的存储格式简称Binary JSON。跟`JSON`一样,支持内嵌的文档对象和数组对象,但是`BSON`有`JSON`没有的数据类型,如`Date`、`BinData`。
`BSON`采用类似于C语言结构体的名称、对表示方法支持内嵌的文档对象和数组对象具有轻量级、可遍历性、高效性的三个特点可以有效地描述非结构化数据和结构化数据。优点是灵活性高但缺点是空间利用率不是很理想。
`BSON`除了支持基本的JSON类型string、integer、boolean、double、null、array、object还有特殊的数据类型date、object id、binary data、regular expression、code。
`BSON`数据类型:
| 数据类型 | 描述 | 举例 |
| ------------- | ------------------------------------------------------------ | ---------------------------------------------------- |
| 字符串 | UTF-8字符串都可以表示为字符串类型的数据 | {"x":"type"} |
| 对象id | 对象id是文档的12字节的唯一ID | {"X":ObjectId()} |
| 布尔值 | true、false | {"x":true} |
| 数组 | 值的集合或列表可以表示的数组 | {"x":["a":"b":"c"]} |
| 32位整数 | 类型不可用。JavaScript仅支持64位浮点数所以32位整数会被 | shell是不支持该类型的shell中默认会转换成64位浮点数 |
| 64位整数 | 不支持这个类型。shell会使用一个特殊的内嵌文档来显示64位 | shell是不支持该类型的shell中默认会转换成64 |
| 64位浮点数 | shell中的数字就是这一种类型 | {"x":3.14159,"y":3} |
| null | 表示空值或者未定义的对象 | {"x":null} |
| undefined | 文档中也可以使用未定义类型 | {"x":undefined} |
| 符号 | shell不支持shell会将数据库中的符号类型的数据自动换成字符串 | |
| 正则表达式 | 文档中可以包含正则表达式采用JavaScript的正则表达式语法 | {"x":/type/i} |
| 代码 | 文档中可以包含JavaScript代码 | {"x":function(){}} |
| 二进制数据 | 二进制数据可以由任意字节的串组成不过shell中无法使用 | |
| 最大值/最小值 | BSON包括一个特殊类型表示可能的最大值。shell中没有这个类型 | |
> shell默认使用64位浮点型数值。对于整型值可以使用`NumberInt`(4字节符号整数)或`NumberLong`(8字节符号整数),如{"x":NumberInt("3")}、{"x":NumberLong("3")}
### 特点
- 高性能
- 高可用
- 高扩展
- 丰富的查询支持
- 无模式(动态模式)、灵活的文档模型
## 二、安装部署
> 这里先介绍单机部署
### windows系统
1. 下载安装包
`MongoDB`提供了可用于32位和64位系统的预编译二进制包。官网下载[社区版](https://www.mongodb.com/try/download/community)On-Premises(内部部署):
![image-20201229155732927](./images/image-20201229155732927.png)
我们选择下载`zip`包格式的压缩包。
> MongoDB 的版本命名规范如x.y.z
>
> y为奇数时表示当前版本为开发版1.5.2、4.1.13
>
> y为偶数时表示当前版本为稳定版1.6.3、4.0.10
>
> z是修正版本号数字越大越好。
2. 解压安装启动
将压缩包解压到一个目录中,手动创建一个目录用于存放数据文件,如`data/db`
启动方式有两种:
- 在`bin`目录下,打开`cmd`命令行,输入命令
```shell
mongod --dbpath=..\data\db
```
这样`MongoDB`会被启动默认端口是27017如果需要修改端口可以添加 `--port`参数来指定端口。
如果缺少`vcruntime140_1.dll`文件会出现下面的错误,解决方式是从网上下载该文件复制到`C:\Windows\System32`目录下就可以。
![image-20201229161245499](./images/image-20201229161245499.png)
- 使用配置文件的方式启动服务
在解压的目录下创建`config`文件夹,并在该目录下新建`mongod.conf`文件,内容如下:
```yml
storage:
dbPath: C:\D\mongodb-win32-x86_64-windows-4.4.2\data\db
```
详细配置文件和配置项。请参考[官方文档](https://docs.mongodb.com/manual/reference/configuration-options/)。
> 注意:
>
> `yml`配置文件中不能使用`tab`键分割字段
>
> 配置文件中如果使用双引号,比如路径地址,自动会将双引号的内容转义。如果不转义,则会报错,解决方法是:
>
> - 将`\`换成`/`或`\\`
> - 如果路径中没有哦空格,则无需加引号
启动命令:
```shell
mongod -f ../config/mongod.conf
mongod --config ../config/mongod.conf
```
更多参数配置:
```yml
systemLog:
#MongoDB发送所有日志输出到的目的地。指定file或syslog。
#如果指定了file还必须指定systemLog.path。
destination: file
path: "/var/log/mongodb/mongod.log"
#每次启动后输出的日志追加到上一次的日志文件中
logAppend: true
storage:
journal:
#启用或禁用持久性日志,以确保数据文件保持有效和可恢复。
#此选项仅在指定存储时适用。dbPath设置。mongod默认启用日志记录。
enabled: true
processManagement:
#启用后台运行mongos或mongod进程的守护模式
fork: true
net:
#主机名和/或IP地址和/或完整的Unix域套接字路径mongos或mongod应该在其上监听客户端连接。
#你可以将mongos或mongod附加到任何接口。要绑定到多个地址,请输入以逗号分隔的值列表。
bindIp: 127.0.0.1
port: 27017
setParameter:
enableLocalhostAuthBypass: false
```
### Shell连接mongo命令
在命令提示符输入`mongo`命令完成登陆
```shell
monogo
mongo --host=127.0.0.1 --port=27017
```
查看已存在的数据库
```shell
show database
```
退出`mongodb`
```shell
exit
```
> MongoDB JavaScript shell 是一个基于JavaScript的解释器所以支持JS程序
### Compass图形化界面客户端
[MongoDB Compass](https://www.mongodb.com/try/download/compass?initial=true)是`MongoDB`数据库的图形化管理工具。可以下载`zip`压缩版本,解压后双击`MongoDBCompass.exe`运行。
![image-20201230093438744](./images/image-20201230093438744.png)
> 标准的[connection-string](https://docs.mongodb.com/manual/reference/connection-string/)格式:`mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]`
>
> 示例:`mongodb://192.168.0.152:27017`
### Linux系统
在`linux`中部署一个单机的`MongoDB`,作为生产环境下使用。安装步骤跟`windows`下的差不多:
1. 下载压缩包,选择`tgz`压缩包的形式。
![image-20201230104714005](./images/image-20201230104714005.png)
2. 上传压缩包至`linux`上,解压
```shell
tar -xvf mongodb-linux-x86_64-ubuntu2004-4.4.2.tgz
```
3. 将解压后的文件移动到指定的目录中
```shell
sudo mv mongodb-linux-x86_64-ubuntu2004-4.4.2 /usr/local/mongodb
```
4. 创建日志和数据的存储目录
```shell
#数据存储目录
sudo mkdir -p /mongodb/single/data/db
#日志目录
sudo mkdir -p /mongodb/single/log
```
5. 创建启动配置文件
```shell
#查看配置参数
/usr/local/mongodb/bin/mongod --help
#新建配置文件
vi /mongodb/single/mongod.conf
```
```yml
systemLog:
destination: file
path: "/home/qiusj/.local/mongodb/singlesite/log/mongodd.log"
logAppend: true
storage:
dbPath: "/home/qiusj/.local/mongodb/singlesite/data/db"
journal:
enabled: true
net:
#添加本机在局域网内的ip
bindIp: 127.0.0.1,192.168.0.152
port: 27017
setParameter:
enableLocalhostAuthBypass: false
processManagement:
fork: true
```
折腾的比较久,发现路径有问题(应该是没有权限,打不开文件)。换了路径解决问题。
6. 启动`MongoDB`服务/usr
```shell
qiusj@u20:~$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/singlesite/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 447853
child process started successfully, parent exiting
```
通过进程来查看服务是否启动:
```shell
qiusj@u20:~$ ps -ef|grep mongod
qiusj 447853 1 0 15:16 ? 00:00:01 /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/mongod.conf
```
7. 使用`mongo`命令和compass工具连接
> 外部链接需要设置防火墙,开放端口
```shell
#ubuntu使用的是ufw,status查看ufw防火墙的状态allow允许扣个端口
sudo ufw status # 查看防火墙状态
sudo ufw allow 27017
```
8. 关闭服务
关闭服务有两种方式:
- 快速关闭:使用`kill`命令直接杀死进程
```shell
#通过进程编号关闭节点
kill -2 进程号
```
- 标准关闭:通过`mongo`个客户端中的`shutdownServer`命令来关闭服务
```shell
#客户端登录服务注意这里通过localhost登录如果需要远程登录必须先登录认证才行。
mongo --port 27017
#切换到admin库
use admin
#如果不是admin数据库,则会报如下错误
shutdown command only works with the admin database; try 'use admin'
#关闭服务
db.shutdownServer()
```
### 数据修复
如果出现数据损坏的情况,可以通过下面操作进行修复:
1. 删除lock文件
```shell
#删除数据目录下面的`.lock`文件
rm -f /data/db/*.lock
```
2. 修复数据
```shell
/usr/local/mongdb/bin/mongod --repair --dbpath=/mongodb/single/data/db
```
## 三、基本命令
### 数据库操作
```shell
#选择和创建数据use 数据库名称
use testdb
#查看有权限查看的所有数据库,磁盘中已经持久化了的数据库
show dbs
show databases
#查看当前数据库
db
#删除数据库
db.dropDatabase()
```
> 如果数据库不存在则会自动创建。自动创建的在没有集合前(空数据库)是存在内存中的。
>
> 默认的数据库是`test`,如果没有选择数据库,集合将存放在`test`数据库中
数据库名称的命名规范,必须是满足以下条件的任意`UTF-8`编码的字符串。
- 不能是空字符串(""
- 不得含有特殊字符(空格、$、/、\、\0
- 全小写
- 最多64字节
**三个默认数据库的特殊作用**
- **`admin`**:从权限的角度,这是`root`数据库,要是将一个用户添加到这个数据库,这个用户将自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或关闭服务器。
- **`local`**:这个数据库中的数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。
- **`config`**:当`Mongo`用于分片设置时,`config`数据库在内部使用,用于保存分片的相关信息。
### 集合操作
集合,类似于关系型数据库中的表。可以显示创建,也可以隐式创建。
```shell
#创建名为article的集合
db.createCollection("article")
#查看当前库中的集合(表)
show collections
show tables
#删除集合,语法db.集合.drop()删除成功会返回true
db.article.drop()
```
集合的命名规范:
- 不能是空字符串""。
- 不能是空字符(\0结尾的字符串
- 不能是以`system.`开头,因为这是给系统集合保留的前缀
- 不能含有保留字符
集合的隐式创建:
当向一个集合插入一个文档的时候,如果集合不存在,则会自动创建集合(这是最常用的方式)。
### 文档的CRUD
文档`document`的数据结构和`JSON`基本一样。所有存储在集合中的数据都是`BSON`格式。
#### 插入
文档的插入的方法有`insert()`、`save()`、`insertMany()`。
`insert`方法的语法:
```shell
db.collection.insert(
<document or array of documents>,
{
writeConcern:<document>,
ordered:<boolean>
}
)
```
参数列表:
| 参数 | 类型 | 说明 |
| -------------- | ------------------------ | ------------------------------------------------------------ |
| `doucment` | document 或document 数组 | 要插入到集合中的文档或文档数组 |
| `writeConcern` | document | Optional. A document expressing the [write concern](https://docs.mongodb.com/manual/reference/write-concern/). Omit to use the default write concern. See [Write Concern](https://docs.mongodb.com/manual/reference/method/db.collection.insert/#insert-wc).<br>Do not explicitly set the write concern for the operation if run in a transaction. To use write concern with transactions, see [Transactions and Write Concern](https://docs.mongodb.com/manual/core/transactions/#transactions-write-concern). |
| `ordered` | boolean | 可选。如果为真,则按顺序插入数组中的文档,如果其中一个文档出现错误,`MongoDB`将返回而不处理数组中的其余文档。如果为假,则执行无序插入,如果其中一个文档出现错误,则继续处理 |
示例:
```shell
> use meface
switched to db meface
> db
meface
> show collections
article
> db.article.insert({
"articleid": "100000",
"content": "今天天气真好,阳光明 媚",
"userid": "1001",
"nickname": "Rose",
"createdatetime": new Date(),
"likenum": NumberInt(10),
"state": null
})
WriteResult({ "nInserted" : 1 })
```
> 如果collection集合不存在则会隐式创建
注意:
- `mongo`中的数字,默认情况下是`double`类型,如果要存储整型,必须使用函数`NumberInt(整型数值)`,否则取出来就有问题。
- 插入当前日期使用`new Date()`。
- 插入的数据没有指定`_id`,会自动生成主键值。
- 如果某字段没值,可以赋值为`null`,或不写该字段。
- 文档中的键/值对是有序的。
- 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个文档)。
- `MongoDB`区分类型和大小写。
- `MongoDB`的文档不能有重复的键。
- 文档的键是字符串除了少数例外情况键可以使用任意UTF-8字符
文档中键的命名规范:
- 键不能含有\0空字符。这个字符用来表示键的结尾。
- `.`和`$`有特殊的意义,只有在特定环境下才能使用。
- 以下划线`_`开头的键是保留的(不是严格要求的)
`insertMany`方法用于批量插入,语法:
```shell
db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)
```
| Parameter | Type | Description |
| :------------- | :------- | :----------------------------------------------------------- |
| `document` | document | An array of documents to insert into the collection. |
| `writeConcern` | document | Optional. A document expressing the [write concern](https://docs.mongodb.com/manual/reference/write-concern/). Omit to use the default write concern.Do not explicitly set the write concern for the operation if run in a transaction. To use write concern with transactions, see [Transactions and Write Concern](https://docs.mongodb.com/manual/core/transactions/#transactions-write-concern). |
| `ordered` | boolean | Optional. A boolean specifying whether the [`mongod`](https://docs.mongodb.com/manual/reference/program/mongod/#bin.mongod) instance should perform an ordered or unordered insert. Defaults to `true`. |
示例:
```shell
#插入多条记录
db.article.insertMany([{
"_id": "1",
"articleid": "100001",
"content": "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我 他。",
"userid": "1002",
"nickname": "相忘于江湖",
"createdatetime": new Date("2019-08- 05T22:08:15.522Z"),
"likenum": NumberInt(1000),
"state": "1"
}, {
"_id": "2",
"articleid": "100001",
"content": "我夏天空腹喝凉开水,冬天喝温开水",
"userid": "1005",
"nickname": "伊人憔 悴",
"createdatetime": new Date("2019-08-05T23:58:51.485Z"),
"likenum": NumberInt(888),
"state": "1"
}, {
"_id": "3",
"articleid": "100001",
"content": "我一直喝凉开水,冬天夏天都喝。",
"userid": "1004",
"nickname": "杰克船 长",
"createdatetime": new Date("2019-08-06T01:05:06.321Z"),
"likenum": NumberInt(666),
"state": "1"
}, {
"_id": "4",
"articleid": "100001",
"content": "专家说不能空腹吃饭,影响健康。",
"userid": "1003",
"nickname": "凯 撒",
"createdatetime": new Date("2019-08-06T08:18:35.288Z"),
"likenum": NumberInt(2000),
"state": "1"
}, {
"_id": "5",
"articleid": "100001",
"content": "研究表明,刚烧开的水千万不能喝,因为烫 嘴。",
"userid": "1003",
"nickname": "凯撒",
"createdatetime": new Date("2019-08- 06T11:01:02.521Z"),
"likenum": NumberInt(3000),
"state": "1"
}]);
```
> 插入时指定了`_id`,则主键为该键值。如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚。由于批量插入时数据量较大,容易出现失败,所以可以使用`try catch`进行异常捕捉处理。
#### 查询
文档的查询方法有很多,先来了解以下两个方法:
- `db.collection.find(query, projection)`:在集合或视图上执行一个查询并返回一个`cursor`游标对象
- `db.collection.findOne(query, projection)`:返回满足`query`条件的第一个文档
| Parameter | Type | Description |
| :----------- | :------- | :----------------------------------------------------------- |
| `query` | document | 可选, 一个`JSON`对象用于筛选文档如查找userId为1001的文档{userId:"10001"} |
| `projection` | document | 可选,可以用于限定文档返回的键,{"articleid":1} |
示例:
```shell
#查询article集合中的所有文档
> db.article.find()
{ "_id" : ObjectId("5fed2b1b232584d8ba357db0"), "articleid" : "100000", "content" : "今天天气真好,阳光明 媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2020-12-31T01:36:27.784Z"), "likenum" : 10, "state" : null }
{ "_id" : "1", "articleid" : "100001", "content" : "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我 他。", "userid" : "1002", "nickname" : "相忘于江湖", "createdatetime" : ISODate("1970-01-01T00:00:00Z"), "likenum" : 1000, "state" : "1" }
...
```
根据查询结果可以看到,当我只用`insert`方法插入时的文档中是没有指定`_id`字段的,但`MongoDB`会为每条文档自动创建该字段作类似于主键的标识,且生成类型为`ObjectID`类型的值。
```shell
#查询articleid为”10000“的文档
> db.article.find({articleid:"100000"})
{ "_id" : ObjectId("5fed2b1b232584d8ba357db0"), "articleid" : "100000", "content" : "今天天气真好,阳光明 媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2020-12-31T01:36:27.784Z"), "likenum" : 10, "state" : null }
#投影查询,限定文档返回的字段(_id字段会默认显示)
> db.article.find({article:"100000"},{articleid:1})
{ "_id" : ObjectId("5fed2b1b232584d8ba357db0"), "articleid" : "100000" }
> db.article.find({articleid:"100000"},{articleid:1,_id:0})
{ "articleid" : "100000" }
#查询所有数据,只显示`_id`、`userid`、`nickname`
> db.article.find({},{userid:1,nickname:1})
```
#### 更新
文档的更新方法有:
- `db.collection.update()`
- `db.collection.updateOne()`
- `db.collection.updateMany()`
`update`方法的语法:
```shell
db.collection.update(query,update,option)
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2
}
)
```
参数列表
| Parameter | Type | Description |
| :------------- | :------------------- | :----------------------------------------------------------- |
| `query` | document | 更新条件,用于筛选出需要更新的文档。可以使用与`find`方法中相同的查询选择器,类似于`where`。在3.0版本中,当`upsert:true`执行`update`时。如果查询使用点表示法在`_id`字段上指定条件,则`MongoDB`将拒绝插入新的文档。 |
| `update` | document or pipeline | 要应用的修改。该值可以是包含更新运算符表达式的文档或仅包含对的替换文档或在MongoDB 4.2中启动聚合管道。管道可以由以下阶段组成: |
| `upsert` | boolean | 可选。如果设置为true则在没有与查询条件匹配的文档时创建新文档。默认值为false如果找不到匹配项则不会插入新文档。 |
| `multi` | boolean | 可选。如果设置为true则更新符合查询条件的多个文档。如果设置为false则更新一个文档。默认值为false。 |
| `writeConcern` | document | 可选。表示写问题的文档。抛出异常的级别。 |
| `collation` | document | 可选。Collation允许用户为字符串比较指定特定于语言的规则例如字母和重音标记的规则。 |
| `arrayFilters` | array | 可选的。筛选器文档的数组,用于确定要为对数组字段进行更新操作而修改哪些数组元素。 |
| `hint` | Document or string | 可选。指定用于支持查询谓词的索引的文档或字符串。该选项可以采用索引规范文档或索引名称字符串。如果指定的索引不存在则说明操作错误。例如请参阅版本4中的“为更新操作指定提示。 |
> 主要关注前四个参数即可
示例:
1. 覆盖的修改
将`_id`为1的记录点赞数量为1001
```shell
> db.article.update({_id:"1"},{likenum:NumberInt(1001)})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.article.find({_id:"1"})
{ "_id" : "1", "likenum" : 1001 }
```
结果是`update`文档将原文档覆盖替换了,其它字段没有了。
2. 局部修改
为了解决上面的问题,需要使用修改器`$set`来实现:
```shell
> db.article.update({_id:"2"},{$set:{likenum:NumberInt(666)}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.article.find({_id:"2"})
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔 悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 666, "state" : "1" }
```
3. 批量修改
更改所有用户名为`1003`的用户的昵称为`小明`。
```shell
# 默认只修改第一个文档(记录)
> db.article.update({userid:"1003"},{$set:{nickname:"小明"}})
# 修改所有符合条件的数据
> db.article.update({userid:"1003"},{$set:{nickname:"小明"}},{multi:true})
```
4. 列值增长的修改
如果想实现对某列值在原有值的基础上进行增加或减少,可以使用`$inc`运算符来实现。
```shell
db.article.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})
```
#### 删除
删除文档的语法结构:
```shell
db.collection.remove(条件)
```
以下语句将会删除集合中全部的数据,慎用
```shell
db.collection.remove({})
```
#### 更多查询
1. `db.collection.count(query,options)`:统计查询
| Parameter | Type | Description |
| --------- | -------- | ---------------------------- |
| `query` | document | 查询条件 |
| `options` | document | 可选,用于修改计数的额外选项 |
```shell
#查询所有记录数
db.article.count()
#统计`userid`为1003的记录数
db.article.count({userid:"10003"})
```
2. `db.collection.find(<query>).limit(<number>)`:对`find`方法返回的`coursor`游标对象使用`limit`方法来指定返回文档的最大数量。默认为20。
```shell
#返回查询结果的前两个文档
db.article.find().limit(2)
```
3. `db.collection.find(<query>).skip(<number>)`在游标上调用skip()方法来控制`MongoDB`从哪里开始返回结果。这种方法在实现分页结果时可能很有用。
```shell
#返回除前三个文档的所有文档
db.article.find().skip(3)
#分页查询第一页12,第二页34,第三页5
#跳过前两条数据取34返回第二页
db.article.find().skip(2).limit(2)
```
4. `db.collection.find(<query>).sort({field: value})`:指定查询返回匹配文档的顺序。并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列。
```shell
#对userid降序排列,并对访问量进行升序排列
db.article.find().sort({userid:-1,likenum:1})
```
> `skip()`, `limilt()`, `sort()`三个放在一起执行的时候,执行的顺序是先 `sort()`, 然后是 `skip()`,最后是显示的` limit()`,和命令编写顺序无关。
5. 模糊查询,`MongoDB`的模糊查询是通过**正则表达式**的方式实现的,格式为:
```shell
db.article.find({field:/正则表达式/})
#查询评论内容包含"开水"的所有文档
db.article.find({content:/开水/})
#查询评论内容以“专家”开头的数据
db.article.find({content:/^专家/})
```
> 正则表达式是JS的语法直接量的写法
6. 比较查询
比较运算符对应的指令:
| 运算符 | 指令 |
| ------ | ------ |
| `<` | `$lt` |
| `<=` | `$lte` |
| `>` | `$gt` |
| `>=` | `$gte` |
| `!=` | `$ne` |
```shell
db.集合名称.find({"field":{$gt:value}}) //大于field > value
db.集合名称.find({"field":{$gte:value}}) //大于等于field >= value
db.集合名称.find({"field":{$lt:value}}) //小于field < value
db.集合名称.find({"field":{$lte:value}}) //小于等于field <= value
db.集合名称.find({"field":{$ne:value}}) //不等于field != value
#查询评论点赞数量大于666的数据
db.article.find({likenum:{$gt:NumberInt(666)}})
```
7. 包含查询使用`$in`指令
```shell
#查询userid包含10021003的文档
db.article.find({userid:{$in:["1003","1002"]}})
```
> 不包含使用`$nin`指令
8. 条件查询
与运算查询同时满足两个以上的条件需要使用`$and`指令将条件进行连接
```shell
$and:[{},{},{}]
#查询评论集合中点赞数likenum大于等于666且小于2000的文档
db.article.find({$and:[{likenum:{$gte:NumberInt(666)}},{likenum:{$lt:NumberInt(2000)}}]},{likenum:1})
```
或运算使用`$or`指令格式与`$and`指令类似
```shell
$or:[{},{},{}]
```
### 常用命令小结
```shell
#选择切换数据库:
use articledb
#插入数据:
db.comment.insert({bson数据})
#查询所有数据:
db.comment.find();
#条件查询数据:
db.comment.find({条件})
#查询符合条件的第一条记录:
db.comment.findOne({条件})
#查询符合条件的前几条记录:
db.comment.find({条件}).limit(条数)
#查询符合条件的跳过的记录:
db.comment.find({条件}).skip(条数)
#修改数据:
db.comment.update({条件},{修改后的数据})
db.comment.update({条件},{$set:{要修改部分的字段:数据})
#修改数据并自增某字段值:
db.comment.update({条件},{$inc:{自增的字段:步进值}})
#删除数据:
db.comment.remove({条件})
#统计查询:
db.comment.count({条件})
#模糊查询:
db.comment.find({字段名:/正则表达式/})
#条件比较运算:
db.comment.find({字段名:{$gt:值}})
#包含查询:
db.comment.find({字段名:{$in:[值1值2]}})
db.comment.find({字段名:{$nin:[值1值2]}})
#条件连接查询:
db.comment.find({$and:[{条件1},{条件2}]})
db.comment.find({$or:[{条件1},{条件2}]})
```
## 四、索引Index
索引支持在`MongoDB`中高效地执行查询如果没有索引`MongoDB`必须执行全集合扫描即扫描集合中的每个文档以选择与查询语句匹配的文档这种扫描全集合的查询效率是非常低的特别在处理大量的数据时查询可以要花费几十秒甚至几分钟这对网站的性能是非常致命的
如果查询存在适当的索引`MongoDB`可以使用该索引限制必须检查的文档数
索引是特殊的数据结构它以易于遍历的形式存储集合数据集的一小部分。**索引存储特定字段或一组字段的值按字段值排序索引项的排序支持有效的相等匹配和基于范围的查询操作**。此外`MongoDB`还可以使用索引中的排序返回排序结果
> `MongoDB`索引使用B树数据结构确切的说是`B-Tree``MySQL`是`B+Tree`
### 索引的类型
#### 单字段索引
`MongoDB`支持在文档的但个字段上创建用户定义的升序/降序索引称为**单字段索引**Single Field Index)。
对于单个字段索引和排序操作索引键的排序顺序即升序或降序并不重要因为`MongoDB`可以在任何方向上遍历索引
![image-20201231220907596](./images/image-20201231220907596.png)
#### 复合索引
`MongoDB`还支持多个字段的用户定义索引**复合索引**Compound Index)。
复合索引中列出的字段顺序具有重要意义例如如果复合索引由`{userid:1,score:-1}`组成则索引首先按`userid`正序排序然后在每个`userid`的值内再按`scroe`倒序排序
![image-20201231221508218](./images/image-20201231221508218.png)
#### 其他索引
- 地理空间索引`Geospatial Index`为了支持对地理空间坐标数据的有效查询`MongoDB`提供了两种特殊的索引返回结果时使用平面几何的**二维索引**和返回结果时使用球面几何的**二维球面索引**。
- 文本索引Text Indexes`MongoDB`提供了一种文本索引类型支持在集合中搜索字符串内容这些文本索引不存储特定于语言的停止词例如"the"、"a"、"or",而将集合中的词作为词干只存储根词
- 哈希索引Hash Indexes为了支持基于散列的分片`MongoDB`提供了散列索引类型它对字段值的散列进行索引这些索引在其范围内的分布更加随机但只支持相等匹配不支持基于范围的查询
### 索引的管理
- `db.collection.getIndexes()`**查看**集合中所有索引的数组该命令运行要求是`MongoDB 3.0+`。
```shell
> db.article.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
```
结果中显示的是默认`_id`索引主键默认会创建唯一值索引
`v`代表的是索引引擎的版本号。`key`表示加了索引的字段`_id``1`表示升序。`name`表示索引的名称默认的方式是索引字段名称后面加下划线。`ns`表示命名空间通常是数据库+集合名称
> `MongoDB`在创建集合的过程中,在`_id`字段上创建一个唯一的索引,默认名字为`_id_`,该索引可防止客户端插入两个具有相同值的文档,不能再`_id`字段上删除此索引。
>
> 该索引是唯一索引,因此值不能重复,即`_id`值不能重复,在分片集群中通常使用`_id`作为片键。
- `db.collection.createIndex(keys,options)`在集合上**创建**索引
参数
| param | type | description |
| --------- | -------- | ------------------------------------------------------------ |
| `keys` | document | 包含字段和值对的文档其中字段是索引键值描述该字段的索引类型对于字段上的升序索引请指定值1对于降序索引请指定值-1比如 {字段:1或-1} 其中1 为指定按升序创建索引如果你想按降序来创建索引指定为 -1 即可另外MongoDB支持几种不同的索引类型包括文本地理空间和哈希索引 |
| `options` | document | 可选包含一组控制索引创建的选项的文档有关详细信息请参见选项详情列表 |
常用`options`选项
| param | type | description |
| ---------- | -------- | ------------------------------------------------------------ |
| background | boolean | 建索引过程会阻塞其它数据库操作background可以指定以后台方式创建索引默认为false |
| unique | boolean | 建立的索引是否唯一指定为true创建唯一索引默认为false |
| name | string | 索引名称 |
| weights | document | 索引的权重数值在1到99999之间表示该索引相对其他索引字段的得分权重 |
> 在 3.0.0 版本前创建索引方法为 `db.collection.ensureIndex() `,之后的版本使用了 `db.collection.createIndex()` 方法,`ensureIndex() `还能用,但只是 `createIndex()` 的别名。
示例
```shell
> db.article.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
#单字段索引对userid字段按升序建立索引
> db.article.createIndex({userid:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
#复合索引对userid字段升序和nickname字段降序建立符合索引
> db.article.createIndex({userid:1,nickname:-1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
> db.article.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"userid" : 1
},
"name" : "userid_1"
},
{
"v" : 2,
"key" : {
"userid" : 1,
"nickname" : -1
},
"name" : "userid_1_nickname_-1"
}
]
```
- `db.collection.dropIndex(index)`移除指定索引
| param | type | description |
| ----- | ------------------ | ------------------------------------------------------------ |
| index | string or document | 指定要删除的索引可以通过索引名称或索引规则文档指定索引若删除文本索引请指定索引名称 |
示例
```shell
> db.article.dropIndex("userid_1")
{ "nIndexesWas" : 3, "ok" : 1 }
> db.article.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"userid" : 1,
"nickname" : -1
},
"name" : "userid_1_nickname_-1"
}
]
```
- `db.collection.dropIndexes()`移除集合中所有索引除了默认`_id_`索引
```shell
> db.article.dropIndexes()
{
"nIndexesWas" : 2,
"msg" : "non-_id indexes dropped for collection",
"ok" : 1
}
> db.article.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
```
### 索引的使用
#### 执行计划
分析查询性能Analyze Query Performance通常使用执行计划解释计划Explain Plan来查看查询的情况如查询耗费的时间是否基于索引查询等
应用场景查看建立的索引是否有效效率如何
语法`db.collection.find(query,option).explain(options)`
示例
```shell
> db.article.find({userid:"1003"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "meface.article",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1003"
}
},
"queryHash" : "37A12FC3",
"planCacheKey" : "37A12FC3",
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"userid" : {
"$eq" : "1003"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "u20",
"port" : 27017,
"version" : "4.4.2",
"gitVersion" : "15e73dc5738d2278b688f8929aee605fe4279b0e"
},
"ok" : 1
}
```
可以看到`winningPlan`中的`stage``COLLSCAN`集合扫描说明没有使用索引
下面创建`userid`索引再执行计划
```shell
> db.article.createIndex({userid:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.article.find({userid:"1003"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "meface.article",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1003"
}
},
"queryHash" : "37A12FC3",
"planCacheKey" : "7FDF74EC",
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"userid" : 1
},
"indexName" : "userid_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"userid" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"userid" : [
"[\"1003\", \"1003\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "u20",
"port" : 27017,
"version" : "4.4.2",
"gitVersion" : "15e73dc5738d2278b688f8929aee605fe4279b0e"
},
"ok" : 1
}
```
可以看到`stage``IXSCAN`基于索引的扫描
在可视化工具`compass`可以更加清除的查看到先从索引集合中扫描抓取匹配的文档
![image-20210104114932126](./images/image-20210104114932126.png)
#### 涵盖查询
涵盖查询Covered Queries当查询条件和查询的投影仅包含索引字段时`MongoDB`直接从索引返回结果而不扫描任何文档或将文档带入内存这些覆盖的查询可以非常的有效
![image-20210104115647955](./images/image-20210104115647955.png)
示例
```shell
> db.article.find({userid:"1003"},{userid:1,_id:0})
{ "userid" : "1003" }
{ "userid" : "1003" }
> db.article.find({userid:"1003"},{userid:1,_id:0}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "meface.article",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1003"
}
},
"queryHash" : "8177476D",
"planCacheKey" : "B632EADC",
"winningPlan" : {
"stage" : "PROJECTION_COVERED",
"transformBy" : {
"userid" : 1,
"_id" : 0
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"userid" : 1
},
"indexName" : "userid_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"userid" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"userid" : [
"[\"1003\", \"1003\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "u20",
"port" : 27017,
"version" : "4.4.2",
"gitVersion" : "15e73dc5738d2278b688f8929aee605fe4279b0e"
},
"ok" : 1
}
```
`compass`工具中查看`Query covered by index`只查询了索引的集合
![image-20210104134546313](./images/image-20210104134546313.png)
## 参考文档
[1] `Collection Methods`https://docs.mongodb.com/manual/reference/method/js-collection/
[2] `Cursor Methods` https://docs.mongodb.com/manual/reference/method/js-cursor/
[3] `MongoDB CRUD Operations` https://docs.mongodb.com/manual/crud/
[4] `Indexes` https://docs.mongodb.com/manual/indexes/
[5] `MongoDB基础入门到高级进阶` https://www.bilibili.com/video/BV1j541187bA