1 快速上手
- 基本应用场景 - “三高”
- High Performance
- High Storage
- High Scalability & Availability
- 具体场景:社交 / 游戏 / 物流 / 物联网 / 视频
- 应用特征:数据量大,读写操作频繁,数据价值低,事务要求低
简介
- Mongo DB 是一个无模式的文档型 · 非关系型数据库
- 使用
BSON
格式( 类JSON
)进行存储,支持比较复杂的数据类型 - Mongo DB 中的记录(对标 row)是一个由键值对构成的「文档」,一个文档被认为是一个 Object。
key
:字符型value
:基本数据类型 / 数组 / 文档 / 文档数组
- 不支持 Join 操作 => 通过嵌入式文档代替多表连接
1 启动
Windows
- 下载 Mongo DB - MSI 版本需要安装,ZIP 版本解压即可使用
- (ZIP)解压文件到指定目录,在 bin 同级目录下创建
data/db
用于存放数据
启动服务
-
命令行方式
mongod --dbpath=..\data\db
指定的 path 可以是绝对路径 / 相对于当前工作路径的相对路径(相对路径好像会寄)
默认端口为 27017,可以通过
--port
参数重新指定端口 -
配置文件方式 (官方文档)
在 bin 同级目录下新建
/config/mongog.conf
参考内容如下: ```yaml storage: # the directory where mongodb instance store data.Default Value
约 3089 个字 140 行代码 预计阅读时间 12 分钟
dbPath: path\to\mongodb\data\db
```
随后通过 `mongod -f ../config/mongod.conf` / `mongod --config` 启动
连接服务
需要另开一个窗口操作(启动服务的窗口需要一直挂着)
在 Mongo DB6 后不再安装 mongo 命令,需要自行安装 mongoshell
- Shell 连接
mongo
/mongo --host=127.0.0.1 --port=27017
- 查看已有数据库
show databases
- 退出
exit
- 更多参数可通过
mongo --help
查看
- 查看已有数据库
- Compass 图形化界面
- 首先要 安装 Compass
- 在 GUI 中输入 Hostname,Port 即可建立连接(使用默认的也行) 新版 Compass 貌似使用 URL 连接的,用默认的就行 连接成功会看到 admin,config,local 三个 database
Linux
- 下载 Monogo DB 的 Linux 版本(tgz版本)
- 使用命令进行解压
tar -xvf xxxxxx.tgz
- 使用命令移到指定目录中
mv xxxxx /usr/local/mongodb
阿巴这里我直接丢到/app
下面去了 -
新建目录用于存储数据和日志(根路径下)
bash # 数据存储目录 mkdir -p /data/db # 日志存储目录 mkdir -p /data/logs # 创建日志文件 touch /data/logs/mongodb.log
因为最新版本又没有命令行了,所以要单独安装(见参考文献)
- 下载压缩包
wget https://downloads.mongodb.com/compass/mongosh-1.6.0-linux-x64.tgz
- 解压到当前路径
tar -zxvf mongosh-1.6.0-linux-x64.tgz
- 进入解压目录
cd mongosh-1.6.0-linux-x64/bin
-
移动 bin 下的两个文件(我丢到
mongodb/bin
下面了)sudo cp mongosh /app/bin/
sudo cp mongosh_crypt_v1.so /app/bin
- 下载压缩包
-
在
/app/bin
下使用/mongosh
,启用连接 可以使用show dbs
打印所有 db 进行测试,默认存在以下三个数据库admin
相当于root
数据库,存放用户及权限。 将用户添加至该数据库后,将继承对所有数据库的权限。 一些特定的服务端命令只能通过该数据库运行(如:show dbs / 关闭服务器)。local
永远不会被集群中的其他服务器复制,用于存储仅限于本地单台服务器使用的集合config
设置分片时,用于保存分片的相关信息
2 基本常用指令
数据库
- 创建并选择数据库
use db_name
若数据库不存在则自动创建(然后选择)- 新创建的数据库仅存在于内存中(无法通过
show dbs
查看) 插入数据后将被持久化到磁盘中 - 数据库命名规则
- 不能是空字符串
- 不能包含:空格
. $ / \ \0
(\0
是空字符) - 全小写
- 最长 64 Byte
- 新创建的数据库仅存在于内存中(无法通过
- 查看(有权限查看的)所有数据库
show dbs / show databases
- 查看正在使用的数据库
db
默认使用test
数据库 -> 创建的 table(集合)将默认存放在test
数据库中 - 删除数据库
db.dropDatabase()
用于删除 已经被持久化 的数据库
集合(表)
-
查看当前 db 下的所有集合
show collections / show tables
-
创建集合
-
显式创建(不管有没有数据,先把表搞出来)
db.createCollection("colection_name")
-
隐式创建(直接插入数据,不存在就建一个表)
- 集合命名规则
- 不能是空串
- 不能包含
\0
-> 用于表示集合名的结束 - 不能以
system.
开头(系统保留前缀)
-
-
删除集合
db.collection.drop() / db.collection_name.drop()
返回值为 bool 类型:成功返回
true
,反之返回false
文档的 CRUD 操作
文档采用 BSON 格式进行存储
插入
-
插入单个文档
使用
insert() / save()
下面是一个例子:db.collection_name.insert( // collection_name 即为待插入的表名(包含隐式创建) <document / array of documents>, // 需要插入的文档/文档数组(JSON格式) { writeConcern: <document>, // 性能及可靠性级别 ordered: <boolean> // 默认为 true // true:顺序插入文档,出错后不处理剩余部分 // false:无序插入,出错后继续处理剩余文档 } )
db.comment.insert( // 向 comment 表中插入数据 { "articleid": "10001", "content": "Ok, fine.", "userid": "27017", "nickname": "Jack", "createtime": new Date(), "likenum": NumberInt(10), "state": null } ) // 返回 writeResult({"nInserted": 1}) 即插入成功
- Mongo DB 中的数字默认为 double 类型,若想存放整型,必须使用函数
NumberInt(数字)
- 使用
nwe Date()
插入当前日期 - 若没有为插入的数据指定
_id
,则会自动生成主键 - 若字段值为空,可以赋值为 null,或直接不写
- Mongo DB 中的数字默认为 double 类型,若想存放整型,必须使用函数
-
批量插入
db.collection_name.insertMany( [<document1>, <document2>, ...], // 待插入的记录 { writeConcern: <document>, ordered: <boolean> } )
下面是一个例子:
db.comment.insertMany( [{ "_id": "1", "articleid": "1001", "content": "the 1st sentence", "userid": "231", "nickname": "Tom", "createtiem": new Date("2019-08-05T22:15.522Z"), "likenum": NumberInt(1000), "state": "1" },{ "_id": "2", "articleid": "1002", "content": "the 2nd sentence", "userid": "235", "nickname": "Jack", "createtiem": new Date("2019-08-18T22:15.522Z"), "likenum": NumberInt(637), "state": "1" }] )
-
若插入时显式制定了
_id
字段的值(ObjectID 或 其他任意支持类型),则以该值作为主键若没有显示指定,则 Mongo DB 会自动创建 ObjectID 类型的
_id
字段作为主键 -
若中间某条数据插入失败,则整个插入操作终止(已经成功的部分不会回滚)
- 由于批量插入时比较容易产生错误,故可以用
try-catch
进行捕获处理
-
查询
基本语法格式:db.collection_name.find(<query>,[projection])
Parameter | Type | Description |
---|---|---|
query |
document | 可选,select all 时忽略/传入空参数{} |
projection |
documrnt | 可选,指定返回的字段(返回所有字段时省略) |
下面是一些例子:
- 查询指定表中的所有数据
db.collection_name.find()
/db.collection_name.find({})
- 查询 ID 为指定值的记录(参数为 JSON 格式)
db.collection_name.find({ key: 'value' })
- 只返回查询的第一条结果(需要用到
findOne
)命令db.collection_name.findOne( { key: 'value' })
- 仅返回查询结果的部分字段内容(需要用到 「Projection Query 投影」)
- 我们查找 userid = 1003 的结果,且仅返回 userid & nickname 字段
上述查询默认会返回
_id
字段,若不想要改数据,可以在第二个参数中进行显式声明
- 我们查找 userid = 1003 的结果,且仅返回 userid & nickname 字段
上述查询默认会返回
更新
db.collection_name.update(query, update, options);
db.collection_name.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
weiteConcern: <document>,
collaction: <document>,
arrayFilters: [<filter1>,...],
hint: <document / string>
}
)
主要关注前四个参数
Parameter | Type | Description |
---|---|---|
query |
document | 选择条件(可使用与 find 相同的查询选择器) |
update |
document | 应用的修改,可以是更新运算符表达式 / 仅包含 key-val 的替换文档 |
upsert |
boolean | 可选,为 true 时在无匹配结果时插入对应的新记录 |
multi |
boolean | 可选,为 true 时更新所有符合要求的记录;默认为 false,仅更新一条匹配的记录 |
writeConcern |
document | 可选,标识抛出异常的级别 |
collation |
document | 可选,指定用于操作的校对规则 |
hint |
document/string | 可选,指定用于支持查询索引的文档或字符串 |
-
覆盖式的修改(使用一个新的记录对象取代旧的记录对象)
db.collection_name.update({ _id:"1"}, {likenum:NumberInt(1001)})
更改完成后,除了
likenum
外的所有字段都会消失 -
局部修改(析构后覆盖部分数据)-> 此处需要使用
$set
方法db.collection_name.update({_id:"1"},{$set:({likenum:NumberInt(1001)}))
此时仅对
likenum
的内容进行修改,其他键值对保持不变 -
批量修改
此处尝试把「所有」
userid="1003"
的用户的nickname
更新为Fynn
默认情况下只修改第一条匹配的记录,为此我们需要设置第三个参数
multi:true
-
键值增长( based on old_value ) 需要使用
$inc
运算符
删除
基本语法格式:db.collection_name.remove(条件)
- 删除表中的「所有记录」- 不删除表本身
db.collection_name.remove({})
- 删除表中
_id="1"
的记录db.collection_name.remove({_id:"1"})
分页查询
统计查询
基本语法:db.collection_name.count(query, option)
- 统计 comment 表中的记录总数
db.comment.count()
- 统计 comment 表中
userid = "1003"
的记录条数db.comment.count({ userid: "1003" })
分页列表查询
基本语法:db.collection_name.find().limit(n_limit).skip(n_skip)
-
我们可以使用
init(N_LIMIT)
方法取出 TOP(N_LIMIT) 条记录,默认为 TOP20db.collection.name.find().limit(3)
-
我们可以使用
skip(N_SKIP)
方法跳过指定数量的记录,默认为 0db.collection_name.skip(3)
下面使用这两个函数来实现「分页查询」
- 假设每页包含 2 条数据
- 从 P2 开始,每一页需要跳过
(n-1)*2
条记录
// 其实可以先用 count 计算最大页数
max_page = floor(db.comment.count() / 2)
// P1
db.comment.find().skip(0).limit(2)
// P2
db.comment.find().skip(2).limit(2)
// P3
db.comment.find().skip(4).limit(2)
排序查询
我们使用 sort()
来指定需要排序的字段,传入 1/-1
分别表示升降序排列:
db.colletion_name.find().sort({ key_name: 1 })
// 设置可以设置基于多列进行排序(第一个key为首要关键字)
db.collection_name.find().sort({
userid: -1,
likenum: 1
}
)
在
limit, skip, sort
同时存在时:首先执行
sort
,随后执行skip
,最后执行limit
-> 与命令书写顺序无关
复杂查询
正则条件查询
-
Mongo DB 的模糊查询是通过正则表达式实现的
-
基本格式:
db.collection_name.find({ key: /正则表达式/ })
-
下面是一些例子:
- 查询所有内容 包含“开水” 的文档
db.comment.find({ content:/开水/ })
- 查询内容 以“专家”开头 的文档
db.comment.find({ content: /^专家/ })
- 查询所有内容 包含“开水” 的文档
比较查询
db.collection.find({"key": { $gt : value}}) // 大于
db.collection.find({"key": { $lt : value}}) // 小于
db.collection.find({"key": { $gte : value}}) // 大于等于
db.collection.find({"key": { $lte : value}}) // 小于等于
db.collection.find({"key": { $ne : value}}) // 不等于
我们可以通过以下语句查询评论数大于700的记录:
db.comment.find({ likenumL { $gt: NumberInt(700) }})
包含查询
-
查询 userid 字段中包含 1003/1004 的文档
$in
db.comment.find({ userid : { $in: ["1003", "1004"]}})
-
查询 userid 字段中不包含 1003/1004 的文档
$nin
db.comment.find({ userid : { $nin: ["1003", "1004"]}})
条件连接查询
-
与
需要记录同时甚至满足多个(>=2)个条件时,使用
$and: [{},{},{}]
我们通过以下语句查询 700 <= likenum < 2000 的评论
-
或
需要记录同时甚至满足多个(>=2)条件时,使用
$or: [{},{},{}]
我们通过以下语句查询 userid = "1003" 或 likenum<1000 的评论
3 索引
- 在缺少索引的情况下,Mongo DB 的每一次查询都会进行「全表扫描」
- Mongo DB 使用 B- Tree 作为做索引(MYSQL 使用 B+ Tree)
索引类型
- 单字段索引 Single Field Index
- Mongo DB 支持用户在文档的单个字段上定义 升序/降序 索引
- 其实升降序本身并不重要 -> Mongo DB 本身可以从任何方向遍历索引
-
符合索引 Compound Index
- 支持在多个字段上定义索引
-
在符合索引中 字段顺序有意义
对于
{userid: 1, score: -1}
,首先按照userid
升序排序;该字段相同时再按照score
降序排序
-
地理空间索引 Geospatial Index
支持对地理空间坐标的有效索引,可以返回 平面/球面几何 的二位索引
-
文本索引 Text Index
支持搜索字符串中包含的 词根(剔除了高频的停止词,并将词干抽象为词根)
-
哈希索引
支持基于散列的分片,对字段的 hash value 进行索引
只支持等值查询,不支持范围查询
索引管理
- 查看某个表上的所有索引
db.collection_name.getIndexes()
创建索引
基础语法:db.collection_name.createIndex(keys, options)
下面是可选 option 参数的信息:
Parameter | Type | Description |
---|---|---|
background | boolean | 可选,建立索引默认会 block 其余的操作,设置为 true 可以更改为后台操作 |
unique | boolean | 可选,建立的索引是否唯一 |
name | string | 索引名称,未指定时 Mongo DB 会基于字段名与排序方式生成一个名称 |
sparse | boolean | 对记录中不存在的字段不启用索引 -> 使用索引将无法查询不包含相应字段的记录 |
expireAfterSeconds | integer | 单位为s,设置表的 TTL |
v | index version | 索引的版本号,默认情况下使用 mongod 创建索引时的运行的版本 |
weights | document | [1,99999],表示相对于其他索引字段的权重 |
default_language | string | 指定文本索引使用的 stop word & 词干规则列表,默认为 EN |
language_override | string | 指定文本索引包含在表中的字段名,覆盖默认的 language ? |
在 3.0.0 以前的版本使用
ensureIndex()
创建索引 现在仍可以使用(只是作为createIndex()
的别名)
- 尝试对
userid
字段建立单字段索引 - 尝试对
userid
&nickname
字段建立复合索引
移除索引
- 移除指定索引
- 基本语法:
db.comment.dropIndex(index)
- 参数
index
可以为 string/document 类型,指定索引名称或索引规范文档 - 删除文本索引必须指定索引名称
- 下面是一个通过索引规范文档删除指定索引的案例:
db.comment.dropIndex({userid:1})
- 基本语法:
- 删除所有索引
- 基本语法:
db.comment.dropIndexes()
- 其中
_id
字段上的索引无法被删除
- 基本语法:
索引使用
执行计划
我们可以通过「支付计划」来查看建立的索引是否有效 / 效果如何
-
基本语法:
db.comment.find(query, options).explain(options)
-
在返回结果中:COLLSCAN 表示“全表扫描”,FETCH 表示“使用索引”
涵盖的查询 Covered Queries
- 当查询 条件&索引 仅包含索引字段时,Mongo DB 将直接从索引中返回结果(而不会尝试扫描/将任何文档读入内存)
- 相关文档见此处