MongoDB 快速入门

108 阅读11分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情

MongoDB 相关概念

业务场景

传统的关系型数据库 (比如 MySQL), 在数据操作的”三高”需求以及对应的 Web 2.0 网站需求面前, 会有”力不从心”的感觉

所谓的三高需求:

高并发, 高性能, 高可用, 简称三高

  • High Performance: 对数据库的高并发读写的要求
  • High Storage: 对海量数据的高效率存储和访问的需求
  • High Scalability && High Available: 对数据的高扩展性和高可用性的需求

而 MongoDB 可以应对三高需求

具体的应用场景:

  • 社交场景, 使用 MongoDB 存储存储用户信息, 以及用户发表的朋友圈信息, 通过地理位置索引实现附近的人, 地点等功能.
  • 游戏场景, 使用 MongoDB 存储游戏用户信息, 用户的装备, 积分等直接以内嵌文档的形式存储, 方便查询, 高效率存储和访问.
  • 物流场景, 使用 MongoDB 存储订单信息, 订单状态在运送过程中会不断更新, 以 MongoDB 内嵌数组的形式来存储, 一次查询就能将订单所有的变更读取出来.
  • 物联网场景, 使用 MongoDB 存储所有接入的智能设备信息, 以及设备汇报的日志信息, 并对这些信息进行多维度的分析.
  • 视频直播, 使用 MongoDB 存储用户信息, 点赞互动信息等.

这些应用场景中, 数据操作方面的共同点有:

  1. 数据量大
  2. 写入操作频繁
  3. 价值较低的数据, 对事务性要求不高

对于这样的数据, 更适合用 MongoDB 来实现数据存储

那么我们什么时候选择 MongoDB 呢?

除了架构选型上, 除了上述三个特点之外, 还要考虑下面这些问题:

  • 应用不需要事务及复杂 JOIN 支持
  • 新应用, 需求会变, 数据模型无法确定, 想快速迭代开发
  • 应用需要 2000 - 3000 以上的读写QPS(更高也可以)
  • 应用需要 TB 甚至 PB 级别数据存储
  • 应用发展迅速, 需要能快速水平扩展
  • 应用要求存储的数据不丢失
  • 应用需要 99.999% 高可用
  • 应用需要大量的地理位置查询, 文本查询

如果上述有1个符合, 可以考虑 MongoDB, 2个及以上的符合, 选择 MongoDB 绝不会后悔.

如果用MySQL呢?

相对MySQL, 可以以更低的成本解决问题(包括学习, 开发, 运维等成本)

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 中, 数据库和集合都不需要手动创建, 当我们创建文档时, 如果文档所在的集合或者数据库不存在, 则会自动创建数据库或者集合

linux安装

cd /usr/local   // 进入安装目录
mkdir MongoDB   // 创建MongoDB文件夹
cd MongoDB    // 进入创建的MongoDB文件夹
mkdir source    // 在MongoDB文件夹下创建source文件夹用于保存安装包
mkdir data    // 在MongoDB文件夹下创建data文件夹用于后续数据库信息保存
mkdir log   // 在MongoDB文件夹下创建log文件夹用于保存数据库日志

将官网下载的源码上传至 source目录

cd /usr/local/MongoDB/source
tar -zxvf 安装包名称 -C /usr/local/MongoDB
cd /usr/local/MongoDB
mv 解压文件夹名称 mongodbServer    // 将解压后的文件夹名称重命名为 mongodbServer

配置

export MONGODB_HOME=/usr/local/MongoDB/mongodbServer
export PATH=$PATH:$MONGODB_HOME/bin
source /etc/profile

配置 MongoDB 启动文件

cd /usr/local/MongoDB/mongodbServer/bin
vim mongod.conf

storage:
    dbPath: "/usr/local/MongoDB/data"
systemLog:
    destination: file
    path: "/usr/local/MongoDB/log/mongod.log"
    logAppend: true
net:
    port: 27017
    bindIpAll: true
processManagement:
    fork: true

启动

mongod --config /usr/local/MongoDB/mongodbServer/bin/mongod.conf

数据库简介

默认保留的数据库

  • admin: 从权限角度考虑, 这是 root 数据库, 如果将一个用户添加到这个数据库, 这个用户自动继承所有数据库的权限, 一些特定的服务器端命令也只能从这个数据库运行, 比如列出所有的数据库或者关闭服务器
  • local: 数据永远不会被复制, 可以用来存储限于本地的单台服务器的集合 (部署集群, 分片等)
  • config: Mongo 用于分片设置时, config 数据库在内部使用, 用来保存分片的相关信息

基本常用命令

数据库 (databases) 管理语法

操作语法
查看所有数据库show dbs;show databases;
查看当前数据库db;
切换到某数据库 (若数据库不存在则创建数据库)use <db_name>;
删除当前数据库db.dropDatabase();

集合 (collection) 管理语法

操作语法
查看所有集合
创建集合db.createCollection("<collection_name>");
删除集合db.<collection_name>.drop()

基本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), 就是文档的唯一标识

如果某条数据插入失败, 将会终止插入, 但已经插入成功的数据不会回滚, 因为批量插入由于数据较多容易出现失败, 因此, 可以使用 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() 查询集合中符合条件的第一个文档, 返回的是一个对象

在查询过滤文档中使用 <字段>:<值> 表达式实现等值查询:

{ <field1>: <value1>, ... }

可以使用 $in 操作符表示范围查询

db.inventory.find( { status: { $in: [ "A", "D" ] } } )

多个查询条件用逗号分隔, 表示 AND 的关系(lt表示小于,lt表示小于,gt表示大于)

db.inventory.find( { status: "A", qty: { $lt: 30 } } )

使用 $or 操作符表示后边数组中的条件是OR的关系

db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } )

联合使用 ANDOR 的查询语句

db.inventory.find( {
     status: "A",
     $or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
} )

<, <=, >, >= 这些操作符也是很常用的, 格式如下:

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 操作符. 示例:查询评论的集合中 userid 字段包含 10031004的文档

db.comment.find({userid:{$in:["1003","1004"]}})

不包含使用 $nin 操作符. 示例:查询评论集合中 userid 字段不包含 10031004 的文档

db.comment.find({userid:{$nin:["1003","1004"]}})

更新update

  • 使用 db.<collection_name>.updateOne(<filter>, <update>, <options>) 方法修改一个匹配 <filter> 条件的文档
  • 使用 db.<collection_name>.updateMany(<filter>, <update>, <options>) 方法修改所有匹配 <filter> 条件的文档
  • 使用 db.<collection_name>.replaceOne(<filter>, <update>, <options>) 方法替换一个匹配 <filter> 条件的文档
  • db.<collection_name>.update(查询对象, 新对象) 默认情况下会使用新对象替换旧对象

其中 <filter> 参数与查询方法中的条件参数用法一致.

如果需要修改指定的属性, 而不是替换需要用“修改操作符”来进行修改

  • $set 修改文档中的制定属性

其中最常用的修改操作符即为$set$unset,分别表示赋值取消赋值.

db.inventory.updateOne(
    { item: "paper" },
    {
        $set: { "size.uom": "cm", status: "P" },
        $currentDate: { lastModified: true }
    }
)
​
db.inventory.updateMany(
    { qty: { $lt: 50 } },
    {
        $set: { "size.uom": "in", status: "P" },
        $currentDate: { lastModified: true }
    }
)
  • uses the $set operator to update the value of the size.uom field to "cm" and the value of the status field to "P",
  • uses the $currentDate operator to update the value of the lastModified field to the current date. If lastModified field does not exist, $currentDate will create the field. See $currentDate for details.

db.<collection_name>.replaceOne() 方法替换除 _id 属性外的所有属性, 其<update>参数应为一个全新的文档.

批量修改

// 默认会修改第一条
db.document.update({ userid: "30", { $set {username: "guest"} } })
​
// 修改所有符合条件的数据
db.document.update( { userid: "30", { $set {username: "guest"} } }, {multi: true} )

列值增长的修改

如果我们想实现对某列值在原有值的基础上进行增加或减少, 可以使用 $inc 运算符来实现

db.document.update({ _id: "3", {$inc: {likeNum: NumberInt(1)}} })

删除delete

  • 使用 db.collection.deleteMany() 方法删除所有匹配的文档.
  • 使用 db.collection.deleteOne() 方法删除单个匹配的文档.
  • db.collection.drop()
  • db.dropDatabase()

文档排序和投影

  • 排序

在查询文档内容的时候, 默认是按照 _id 进行排序

我们可以用 $sort 更改文档排序规则

db.posts.find().sort({ title : -1 }).limit(2)
  • 投影

有些情况, 我们对文档进行查询并不是需要所有的字段, 比如只需要 id 或者 用户名, 我们可以对文档进行“投影”

  • 1 - display
  • 0 - dont display
> db.users.find( {}, {username: 1} )
​
> db.users.find( {}, {age: 1, _id: 0} )

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)

为了支持对地理空间坐标数据的有效查询, 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)
    
  • 索引的删除

    # 删除某一个索引
    $ db.collection.dropIndex(index)
    ​
    # 删除全部索引
    $ db.collection.dropIndexes()