mongodb基础教程
知识点
- 理解 MongoDB 的业务场景, 熟悉 MongoDB 的简介, 特点和体系结构, 数据类型等.
- 能够通过docker-compose创建并启动mongodb和mongo-express
- 掌握 MongoDB 基本常用命令实现数据的 CRUD
- 掌握 MongoDB 的索引类型, 索引管理, 执行计划
docker-compose部署mongodb和mongo-express
业务场景
传统的关系型数据库 (比如 MySQL), 在数据操作的”三高”需求以及对应的 Web 2.0 网站需求面前, 会有”力不从心”的感觉
所谓的三高需求:
高并发, 高性能, 高可用, 简称三高
- High Performance: 对数据库的高并发读写的要求
- High Storage: 对海量数据的高效率存储和访问的需求
- High Scalability && High Available: 对数据的高扩展性和高可用性的需求
而 MongoDB 可以应对三高需求
具体的应用场景:
- 社交场景, 使用 MongoDB 存储存储用户信息, 以及用户发表的朋友圈信息, 通过地理位置索引实现附近的人, 地点等功能.
- 游戏场景, 使用 MongoDB 存储游戏用户信息, 用户的装备, 积分等直接以内嵌文档的形式存储, 方便查询, 高效率存储和访问.
- 物流场景, 使用 MongoDB 存储订单信息, 订单状态在运送过程中会不断更新, 以 MongoDB 内嵌数组的形式来存储, 一次查询就能将订单所有的变更读取出来.
- 物联网场景, 使用 MongoDB 存储所有接入的智能设备信息, 以及设备汇报的日志信息, 并对这些信息进行多维度的分析.
- 视频直播, 使用 MongoDB 存储用户信息, 点赞互动信息等.
这些应用场景中, 数据操作方面的共同点有:
- 数据量大
- 写入数据频繁(读写都很频繁)
- 价值较低的数据,对事务性要求不高
那么我们什么时候选择 MongoDB 呢?
除了架构选型上, 除了上述三个特点之外, 还要考虑下面这些问题:
- 应用不需要事务及复杂 JOIN 支持
- 新应用, 需求会变, 数据模型无法确定, 想快速迭代开发
- 应用需要 2000 - 3000 以上的读写QPS(更高也可以)
- 应用需要 TB 甚至 PB 级别数据存储
- 应用发展迅速, 需要能快速水平扩展
- 应用要求存储的数据不丢失
- 应用需要 99.999% 高可用
- 应用需要大量的地理位置查询, 文本查询
如果上述有1个符合, 可以考虑 MongoDB, 2个及以上的符合, 选择 MongoDB 绝不会后悔.
mongodb简介
MongoDB是一个开源, 高性能, 无模式的文档型数据库, 当初的设计就是用于简化开发和方便扩展, 是NoSQL数据库产品中的一种.是最 像关系型数据库(MySQL)的非关系型数据库. 它支持的数据结构非常松散, 是一种类似于 JSON 的 格式叫BSON, 所以它既可以存储比较复杂的数据类型, 又相当的灵活. MongoDB中的记录是一个文档, 它是一个由字段和值对(field:value)组成的数据结构.MongoDB文档类似于JSON对象, 即一个文档认 为就是一个对象.字段的数据类型是字符型, 它的值除了使用基本的一些类型外, 还可以包括其他文档, 普通数组和文档数组.
“最像关系型数据库的 NoSQL 数据库”. MongoDB 中的记录是一个文档, 是一个 key-value pair. 字段的数据类型是字符型, 值除了使用基本的一些类型以外, 还包括其它文档, 普通数组以及文档数组
MongoDB 数据模型是面向文档的, 所谓文档就是一种类似于 JSON 的结构, 简单理解 MongoDB 这个数据库中存在的是各种各样的 JSON(BSON)
- 数据库 (database): 数据库是一个仓库, 存储集合 (collection)
- 集合 (collection): 类似于数组, 在集合中存放文档
- 文档 (document): 文档型数据库的最小单位, 通常情况, 我们存储和操作的内容都是文档
在 MongoDB 中, 数据库和集合都不需要手动创建, 当我们创建文档时, 如果文档所在的集合或者数据库不存在, 则会自动创建数据库或者集合
数据库管理语法
操作 | 语法 |
---|---|
查看所有数据库 | show dbs; 或 show databases; |
查看当前数据库 | db; |
切换到某数据库 (若数据库不存在则创建数据库) | use <db_name>; |
删除当前数据库 | db.dropDatabase(); |
集合管理语法
操作 | 语法 |
---|---|
查看所有集合 | show collections; |
创建集合 | db.createCollection("<collection_name>"); |
删除集合 | db.<collection_name>.drop() |
数据模型
终端连接mongodb
mongosh
用户名和密码登录mongodb
use admin
db.auth(username, password)
基本常用命令
数据库操作
默认保留的数据库
- admin: 从权限角度考虑, 这是 root 数据库, 如果将一个用户添加到这个数据库, 这个用户自动继承所有数据库的权限, 一些特定的服务器端命令也只能从这个数据库运行, 比如列出所有的数据库或者关闭服务器
- local: 数据永远不会被复制, 可以用来存储限于本地的单台服务器的集合 (部署集群, 分片等)
- config: Mongo 用于分片设置时, config 数据库在内部使用, 用来保存分片的相关信息
$ show dbs
$ use articledb
$ show dbs
当使用 use articledb 的时候. articledb 其实存放在内存之中, 当 articledb 中存在一个 collection 之后, mongo 才会将这个数据库持久化到硬盘之中.
文档基本CRUD
官方文档:https://www.mongodb.com/docs/manual/crud/
- 创建Create
创建或插入操作将新文档添加到集合中。如果集合当前不存在,则插入操作将自动创建集合。
- 使用 db.<collection_name>.insertOne() 向集合中添加一个文档, 参数一个 json 格式的文档
- 使用 db.<collection_name>.insertMany() 向集合中添加多个文档, 参数为 json 文档数组
// 向集合中添加一个文档
db.collection.insertOne({item:"canvas", qty:100, tags:["cotton"], size:{h:28, w:35.5, uom:"cm"}})
// 向集合中添加多个文档
db.collection.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], size: { h: 14, w: 21, uom: "cm" } },
{ item: "mat", qty: 85, tags: ["gray"], size: { h: 27.9, w: 35.5, uom: "cm" } },
{ item: "mousepad", qty: 25, tags: ["gel", "blue"], size: { h: 19, w: 22.85, uom: "cm" } }
])
注:当我们向 collection 中插入 document 文档时, 如果没有给文档指定 _id 属性, 那么数据库会为文档自动添加 _id field, 并且值类型是 ObjectId(blablabla), 就是文档的唯一标识, 类似于 relational database 里的 primary key
mongo 中的数字, 默认情况下是 double 类型, 如果要存整型, 必须使用函数 NumberInt(整型数字), 否则取出来就有问题了
插入当前日期可以使用 new Date()
如果某条数据插入失败, 将会终止插入, 但已经插入成功的数据不会回滚掉. 因为批量插入由于数据较多容易出现失败, 因此, 可以使用 try catch 进行异常捕捉处理, 测试的时候可以不处理.如:
try {
db.comment.insertMany([
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上, 健康很重要, 一杯温水幸福你我 他.","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-0805T22: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-0806T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);
} catch (e) {
print (e);
}
- 查询read
- 使用 db.<collection_name>.find() 方法对集合进行查询, 接受一个 json 格式的查询条件. 返回的是一个数组
- db.<collection_name>.findOne() 查询集合中符合条件的第一个文档, 返回的是一个对象
可以使用$in操作符表示范围查询:
db.collection.find({item: {$in: ["canvas", "mat"]}})
多个查询条件用逗号分隔,表示and的关系:
db.collection.find({item: "canvas", qty: {$lte: 35}})
等价于下面sql语句:SELECT * FROM collection WHERE item = "canvas" AND qty <= 35
使用 $or 操作符表示后边数组中的条件是OR的关系:
db.collection.find({$or: [{item: "mat"}, {qty: 25}]})
等价于下面 sql 语句
SELECT * FROM collection WHERE item = "mat" OR qty = 25
联合使用and和or的查询语句:
db.collection.find({qty: {$gte: 35}, $or: [{item: "mat"}, {item: "cbd"}]})
使用正则表达式:
以什么开头:db.collection.find({item: /^m/})
以什么结尾:db.collection.find({item: /m$/})
包含什么:db.collection.find({item: /m/})
- 更新Update
- 使用 db.<collection_name>.updateOne(
, , ) 方法修改一个匹配 条件的文档 - 使用 db.<collection_name>.updateMany(
, , ) 方法修改所有匹配 条件的文档 - 使用 db.<collection_name>.replaceOne(
, , ) 方法替换一个匹配 条件的文档 - db.<collection_name>.update(查询对象, 新对象) 默认情况下会使用新对象替换旧对象
其中参数与查询方法中的条件参数用法一致.
如果需要修改指定的属性, 而不是替换需要用“修改操作符”来进行修改
$set
修改文档中的指定属性
其中最常用的修改操作符即为$set和$unset,分别表示赋值和取消赋值.
db.collection.updateOne({item: "cbd"}, {$set: {"size.uom": "mm", qty: 45}, $currentDate: {lastModified: true}})
db.collection.updateMany({qty: {$lt: 50}}, {$set: {"size.uom": "in", "size.h": 20}, $currentDate: {lastModified: true}})
使用 $set 运算符将 size.uom 字段的值更新为“ cm”,将 status 字段的值更新为“ P”,使用 $currentDate 运算符将 lastAmendment 字段的值更新为当前日期。如果 lastAmendment 字段不存在,$currentDate 将创建该字段。有关详细信息,请参见 $currentDate。
db.<collection_name>.replaceOne() 方法替换除 _id 属性外的所有属性, 其
db.collection.replaceOne({item: "cbd"}, {item: "abc", instock: [{warehouse: "A", qty: 60}, {warehouse: "B", qty: 40}]})
批量修改
// 默认会修改第一条
db.collection.update({item: "abc"}, {$set: {qty: 40}})
// 修改所有符合条件的数据
db.collection.update({item: "abc"}, {$set: {qty: 30}}, {multi: true})
列值增长的修改
如果我们想实现对某列值在原有值的基础上进行增加或减少, 可以使用 $inc 运算符来实现
// 更新查找到的第一个文档
db.collection.update({item: "abc"}, {$inc: {qty: 1}})
// 更新查找到的所有文档
db.collection.update({item: "abc"}, {$inc: {qty: 1}}, {multi: true})
修改操作符
Name | Description |
---|---|
$currentDate | 将字段的值设置为当前日期(日期或时间戳)。 |
$inc | 将字段的值增加指定的数量。 |
$min | 仅当指定值小于现有字段值时才更新字段。 |
$max | 仅当指定值大于现有字段值时才更新字段。 |
$mul | 将字段值乘以指定的数量。 |
$rename | 重命名字段。 |
$set | 设置文档中字段的值。 |
$setOnInsert | 如果更新导致插入文档,则设置字段的值。对修改现有文档的更新操作没有影响。 |
$unset | 从文档中移除指定的字段。 |
删除Delete
- 使用 db.collection.deleteMany() 方法删除所有匹配的文档.
- 使用 db.collection.deleteOne() 方法删除单个匹配的文档.
- db.collection.drop()
- db.dropDatabase()
db.collection.deleteMany({qty: {$lt: 40}})
一般数据库中的数据都不会真正意义上的删除, 会添加一个字段, 用来表示这个数据是否被删除
文档排序和投影
- 排序Sort
在查询文档内容的时候, 默认是按照 _id 进行排序
我们可以用 $sort 更改文档排序规则
db.collection.find().sort({qty: 1}).limit(1)
- 投影Projection
有些情况, 我们对文档进行查询并不是需要所有的字段, 比如只需要 id 或者 用户名, 我们可以对文档进行“投影”
- 1 - display
- 0 - dont display
db.collection.find({}, {item: 1})
db.collection.find({}, {item: 1, _id: 0})
- forEach()
test> db.collection.find().forEach(function(doc) {print("Blog Post:" + doc.item)})
Blog Post:canvas
Blog Post:abc
文档的分页查询
- 统计查询
// 集合中的所有文档个数
db.comment.countDocuments()
// 指定查询条件计数
db.comment.countDocuments({userid: "1003"})
- 分页列表查询
// 查找第一页
db.comment.find().limit(2).skip(0)
// 第二页
db.comment.find().limit(2).skip(2)
- 排序查询
// 升序:1, 降序:-1
db.comment.find().sort({likenum: -1})
// 投影查询,只显示_id和userid,并且userid按照升序排序
db.comment.find({}, {userid:1}).sort({userid: 1})‘’
// 优先按照userid升序排序,其次按照点赞数降序排序
db.comment.find({}, {userid: 1}).sort({userid: 1, likenum: -1})
其它查询方式
- 正则表达式
$ db.collection.find({field:/正则表达式/})
$ db.collection.find({字段:/正则表达式/})
- 比较查询
<, <=, >, >= 这些操作符也是很常用的, 格式如下:
db.collection.find({ "field" : { $gt: value }}) // 大于: field > value
db.collection.find({ "field" : { $lt: value }}) // 小于: field < value
db.collection.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.collection.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.collection.find({ "field" : { $ne: value }}) // 不等于: field != value
-
包含查询
包含使用 $in 操作符. 示例:查询集合中 item字段等于canvas 和 abc的文档
db.collection.find({item: {$in: ["canvas", "abc"]}})
不包含使用 $nin:
db.collection.find({item: {$nin: ["abc"]}})
-
条件链接查询
// 查询点赞数大于等于700,且小于2000的文档
db.comment.find({$and: [{likenum: {$gte: 700}}, {likenum: {$lt: 2000}}]})
// 查询userid为1003,或点赞数小于1000的文档记录
db.comment.find({$or: [{userid: "1003"}, {likenum: {$lt: 1000}}]})
常用命令小结
选择切换数据库: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}]})
mongodb的索引
- 概述
索引支持在 MongoDB 中高效地执行查询.如果没有索引, MongoDB 必须执行全集合扫描, 即扫描集合中的每个文档, 以选择与查询语句 匹配的文档.这种扫描全集合的查询效率是非常低的, 特别在处理大量的数据时, 查询可以要花费几十秒甚至几分钟, 这对网站的性能是非常致命的.
如果查询存在适当的索引, MongoDB 可以使用该索引限制必须检查的文档数.
索引是特殊的数据结构, 它以易于遍历的形式存储集合数据集的一小部分.索引存储特定字段或一组字段的值, 按字段值排序.索引项的排 序支持有效的相等匹配和基于范围的查询操作.此外, MongoDB 还可以使用索引中的排序返回排序结果.
MongoDB 使用的是 B Tree, MySQL 使用的是 B+ Tree
// create index
db.<collection_name>.createIndex({ userid : 1, username : -1 })
// retrieve indexes
db.<collection_name>.getIndexes()
// remove indexes
db.<collection_name>.dropIndex(index)
// there are 2 ways to remove indexes:
// 1. removed based on the index name
// 2. removed based on the fields
db.<collection_name>.dropIndex( "userid_1_username_-1" )
db.<collection_name>.dropIndex({ userid : 1, username : -1 })
// remove all the indexes, will only remove non_id indexes
db.<collection_name>.dropIndexes()
- 索引的类型
MongoDB 支持在文档的单个字段上创建用户定义的升序/降序索引, 称为单字段索引 Single Field Index
对于单个字段索引和排序操作, 索引键的排序顺序(即升序或降序)并不重要, 因为 MongoDB 可以在任何方向上遍历索引.
- 复合索引
MongoDB 还支持多个字段的用户定义索引, 即复合索引 Compound Index
复合索引中列出的字段顺序具有重要意义.例如, 如果复合索引由 { userid: 1, score: -1 } 组成, 则索引首先按 userid 正序排序, 然后 在每个 userid 的值内, 再在按 score 倒序排序.
- 其它索引
- 地理空间索引 Geospatial Index
- 文本索引 Text Indexes
- 哈希索引 Hashed Indexes
地理空间索引(Geospatial Index)
为了支持对地理空间坐标数据的有效查询, MongoDB 提供了两种特殊的索引: 返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引.
文本索引(Text Indexes)
MongoDB 提供了一种文本索引类型, 支持在集合中搜索字符串内容.这些文本索引不存储特定于语言的停止词(例如 “the”, “a”, “or”), 而将集合中的词作为词干, 只存储根词.
哈希索引(Hashed Indexes)
为了支持基于散列的分片, MongoDB 提供了散列索引类型, 它对字段值的散列进行索引.这些索引在其范围内的值分布更加随机, 但只支持相等匹配, 不支持基于范围的查询.
索引的管理操作
- 索引的查看
db.collection.getIndexes()
默认 _id 索引: MongoDB 在创建集合的过程中, 在 _id 字段上创建一个唯一的索引, 默认名字为 _id , 该索引可防止客户端插入两个具有相同值的文 档, 不能在 _id 字段上删除此索引.
注意:该索引是唯一索引, 因此值不能重复, 即 _id 值不能重复的.
在分片集群中, 通常使用 _id 作为片键.
- 索引的创建
db.collection.createIndex(keys, options)
参数:
options(更多选项)列表:
注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex() , 之后的版本使用了 db.collection.createIndex() 方法, ensureIndex() 还能用, 但只是 createIndex() 的别名.
举个🌰:
db.collection.createIndex({item: 1, qty: -1})
- 索引的删除
// 删除某一索引
db.collection.dropIndex(index)
// 删除全部索引
db.collection.dropIndexes()
提示:_id 的字段的索引是无法删除的, 只能删除非 _id 字段的索引
db.collection.dropIndex(item_1_qty_-1)
db.collection.dropIndex({item: 1, qty: -1})
上面两种删除方法,效果一致。
索引使用
- 执行计划
分析查询性能 (Analyze Query Performance) 通常使用执行计划 (解释计划 - Explain Plan) 来查看查询的情况
db.collection.find().explain()
未添加索引之前
"stage" : "COLLSCAN", 表示全集合扫描
点击查看代码
{
explainVersion: '1',
queryPlanner: {
namespace: 'test.collection',
indexFilterSet: false,
parsedQuery: { item: { '$eq': 'abc' } },
queryHash: '8545567D',
planCacheKey: '8545567D',
maxIndexedOrSolutionsReached: false,
maxIndexedAndSolutionsReached: false,
maxScansToExplodeReached: false,
winningPlan: {
stage: 'COLLSCAN',
filter: { item: { '$eq': 'abc' } },
direction: 'forward'
},
rejectedPlans: []
},
command: { find: 'collection', filter: { item: 'abc' }, '$db': 'test' },
serverInfo: {
host: '1cf9552cf597',
port: 27017,
version: '6.0.2',
gitVersion: '94fb7dfc8b974f1f5343e7ea394d0d9deedba50e'
},
serverParameters: {
internalQueryFacetBufferSizeBytes: 104857600,
internalQueryFacetMaxOutputDocSizeBytes: 104857600,
internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
internalDocumentSourceGroupMaxMemoryBytes: 104857600,
internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
internalQueryProhibitBlockingMergeOnMongoS: 0,
internalQueryMaxAddToSetBytes: 104857600,
internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600
},
ok: 1
}
添加索引之后:
stage: 'IXSCAN'
基于索引的扫描。
涵盖的查询
当查询条件和查询的投影仅包含索引字段是, MongoDB 直接从索引返回结果, 而不扫描任何文档或将文档带入内存, 这些覆盖的查询十分有效