Loading...

MongoDB 基础教程

工作中的项目用到 MongoDB 的地方还是不少的,只是前几个月都比较忙碌,一直没腾出时间好好梳理一下。这两周稍微轻松些了,就决定把 MongoDB 这块知识过一遍,也方便以后参考(实在讨厌各大博客网站的转载机制,劣质无用的文章被转载的到处都是,真正遇到问题时,想要找到一篇具有参考性的文章实在太难)。由于工作中暂无 MongoDB 集群的使用场景,因此本文暂未整理分片集群这块的知识(标记为 TODO,或许日后会补充)。

mongodb-logo

NoSQL 简介

NoSQL,指的是非关系型的数据库。NoSQL 是 Not Only SQL 的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。NoSQL 通常用于超大规模数据的存储(如谷歌或 Facebook 每天为他们的用户收集万亿比特的数据),这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

NoSQL 优缺点

优点:

  • 高可扩展性
  • 分布式计算
  • 低成本
  • 架构的灵活性,半结构化数据
  • 没有复杂的关系

缺点:

  • 没有标准化
  • 有限的查询功能(到目前为止)
  • 最终一致是不直观的程序

RDBMS vs NoSQL

关系型数据库(RDBMS)与非关系型数据库(NoSQL)对比:

对比项 RDBMS NoSQL
存储格式 表格式,行和列 文档、键值对、图结构
存储规范 规范性,避免重复 鼓励冗余
存储扩展 纵向扩展(横向扩展有限) 横向扩展,分布式
查询方式 结构化查询 非结构化查询语言SQL
事务 支持事务 不支持事务一致性
性能 读写性能差 读写性能高
成本 成本高 简单易部署,开源,成本低
存储方式 数据主要存储在磁盘中 数据主要存储在内存中(部分可以持久化到磁盘)
建表原则 依靠关系模型构建关联 数据模型比较简单,用K/V的形式来存储数据

MongoDB 简介

MongoDB 是由 C++ 语言编写的,是一个基于分布式文件存储的开源非关系型数据库系统。在高负载的情况下,添加更多的节点,可以保证服务器性能。MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

MongoDB 使用 BSON(Binary JSON)对象来存储,与 JSON 格式的键值对(key/value)类似,字段值可以包含其他文档,数组及文档数组。支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系型数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

MongoDB 主要特点

  • MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。
  • 你可以在MongoDB记录中设置任何属性的索引 (如:FirstName=“Sameer”,Address=“8 Gandhi Road”)来实现更快的排序。
  • 你可以通过本地或者网络创建数据镜像,这使得MongoDB有更强的扩展性。
  • 如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。
  • Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。
  • MongoDB 使用update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。
  • MongoDB 中的Map/reduce主要是用来对数据进行批量处理和聚合操作。
  • Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。
  • Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。
  • GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。
  • MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
  • MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。
  • MongoDB安装简单。

MongoDB 数据类型

数据类型 描述
String 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。
Integer 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
Boolean 布尔值。用于存储布尔值(真/假)。
Double 双精度浮点值。用于存储浮点值。
Min/Max keys 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。
Arrays 用于将数组或列表或多个值存储为一个键。
Timestamp 时间戳。记录文档修改或添加的具体时间。
Object 用于内嵌文档。
Null 用于创建空值。
Symbol 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
Date 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
Object ID 对象 ID。用于创建文档的 ID。
Binary Data 二进制数据。用于存储二进制数据。
Code 代码类型。用于在文档中存储 JavaScript 代码。
Regular expression 正则表达式类型。用于存储正则表达式。

MongoDB 支持语言

mongodb_lang

下载与安装

Windows 环境

mongo_download

双击可执行文件 .msi ,进行安装(基本上下一步即可)。

推荐选择自定义安装:

mongo_install

推荐取消默认的 Install MongoDB Compass 安装。

MongoDB 配置:

  1. 环境变量

    编辑系统变量 Path 并添加 MongoDB 的 bin 目录位置。例如:A:\MongoDB\Server\5.0\bin

  2. 创建数据库文件的存放位置

    创建文件夹 [MongoDB]\data\db

    这是因为 MongoDB 服务在启动之前必须创建数据库文件的存放文件夹,否则命令不会自动创建,且服务无法启动成功。

  3. 启动 MongoDB 服务

    cmd 命令进入安装目录 [MongoDB]\data\bin 位置,执行如下命令:

    A:\MongoDB\Server\5.0\bin> mongod --dbpath A:\MongoDB\Server\5.0\data\db

    浏览器访问:http://127.0.0.1:27017,如果出现如下信息,则表示配置成功且服务已启动:

    It looks like you are trying to access MongoDB over HTTP on the native driver port.

  4. 配置本地 MongoDB 服务

    创建文件夹 [MongoDB]\data\log ,用于存放日志文件。

    创建配置文件 [MongoDB]\mongo.config

    dbpath=A:\MongoDB\Server\5.0\data\db
    logpath=A:\MongoDB\Server\5.0\data\log\mongo.log

    使用管理员角色执行如下命令:

    # 停止已启动的MongoDB服务
    net stop MongoDB
    # 移除MongoDB服务
    sc delete MongoDB
    # 安装MongoDB服务
    mongod -dbpath "A:\MongoDB\Server\5.0\data\db" -logpath "A:\MongoDB\Server\5.0\data\log\mongo.log" -install -serviceName "MongoDB"
    # 启动MongoDB服务
    net start MongoDB

    浏览器访问:http://127.0.0.1:27017

    客户端连接:

    A:\MongoDB\Server\5.0\bin> mongo.exe

Linux 环境

  1. 卸载删除

    # 停止服务
    ps -ef | grep mongo
    kill -9 xxx
    net stop mongodb
    sudo yum erase $(rpm -qa | grep mongodb-org) #卸载MongoDB
    sudo rm -r /var/log/mongodb #删除日志文件
    sudo rm -r /var/lib/mongo  #删除数据文件
  2. 安装

    配置 yum 源:

    cd /etc/yum.repos.d
    vi mongodb-org-5.0.repo
    # 版本选择可前往 https://repo.mongodb.org 进行查看。

    编辑如下内容(版本号视个人需要而定):

    [mongodb-org-5.0]
    name=MongoDB Repository
    baseurl=https://repo.mongodb.org/yum/redhat/7Server/mongodb-org/5.0/x86_64/
    gpgcheck=1
    enabled=1
    gpgkey=https://www.mongodb.org/static/pgp/server-5.0.asc

    提示:具体版本,可前往源地址进行查看选择:https://repo.mongodb.org

    执行安装:

    yum install -y mongodb-org

    修改配置:

    vim /etc/mongod.conf
    
    # network interfaces
    net:
      port: 27017
      bindIp: 0.0.0.0  # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.

    基本命令:

    systemctl start mongod.service  # 启动
    systemctl status mongod.service  # 查看状态
    systemctl stop mongod.service   # 停止
    systemctl enable mongod.service  # 自启

    测试连接:

    mongo 127.0.0.1:27017 # 如果是默认IP端口,可以不指定

    权限控制:

    use admin # 其他数据库也类似
    switched to db admin
    db.createUser({ user:"admin", pwd:"123456", roles:["root"] })
    Successfully added user: { "user" : "admin", "roles" : [ "root" ] }
    db.auth("admin", "123456")
    1
    exit
    bye
    mongo -u root -p 123456 # 账号登录

    启用身份验证:

    vi /etc/mongod.conf
       
    security:
         authorization: enabled   # disable or enabled
  3. 问题排查

    • MongoDB 服务启动失败,可以使用 journalctl -xe 命令跟踪更加详细的信息,通常是由于以下几种原因导致的:

      1. mongod.conf 配置内容或语法错误。
      2. 日志访问权限不足。
      3. 锁定
    • 如果服务器本地可以访问 MongoDB,但远程访问失败,请检查:

      1. 检查配置文件 /etc/mongod.conf IP 是否放开、是否允许远程访问,以及访问账号密钥信息。
      2. 云服务器需注意配置安全组规则。
      3. 如已开启防火墙,需添放开对应端口。
    • 命令提示

      # 查看与修改
      vi /etc/mongod.conf # MongoDB 配置文件
      cat /usr/lib/systemd/system/mongod.service # MongoDB 服务
      # 权限或mongodb锁文件
      sudo chown -Rc mongodb. /var/log/mongodb
      sudo chown -Rc mongodb. /var/lib/mongo
      rm -rf /var/lib/mongo/mongod.lock
      rm -rf /tmp/mongodb-27017.sock
      # 防火墙
      firewall-cmd --state # 查看防火墙状态
      systemctl stop firewalld.service # 关闭防火墙
      systemctl start firewalld.service # 开启防火墙
      firewall-cmd --add-port=27017/tcp --permanent # 防火墙放开指定端口(永久)
      firewall-cmd --zone=public --list-ports # 查看防火墙已开放端口
      firewall-cmd --reload # 防火墙配置重新载入

      配置参考:https://docs.mongodb.com/manual/reference/configuration-options

MongoDB GUI

术语与概念

SQL 数据库与 MongoDB 数据库术语及概念的对比:

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

数据库

一个 mongodb 中可以建立多个数据库。MongoDB的默认数据库为 db ,该数据库存储在data目录中,这一点我们在安装配置时已有提及。

MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。

> show dbs # 显示所有数据列表
admin   0.000GB
config  0.000GB
local   0.000GB
> db # 显示当前数据库对象或集合
test
> use local # 切换到指定数据库
switched to db local

MongoDB 默认有三个数据库:

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

文档

文档是一组键值对(即 BSON),示例:

{"site":"www.chinmoku.cc", "author":"Chinmoku"}

RDBMS 与 MongoDB 术语对比:

RDBMS MongoDB
数据库 数据库
表格 集合
文档
字段
表联合 嵌入文档
主键 主键(MongoDB 提供了 key 为 _id)

注意:

  1. 文档中的键值对是有序的。
  2. 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
  3. MongoDB 区分类型和大小写。
  4. MongoDB 的文档不能有重复的键。
  5. 文档的键是字符串,除少数例外情况,键可以使用任意 UTF-8 字符。

文档键命名规范:

  • 键不能含有空字符。这个字符用来表示键的结尾。
  • .$ 有特别的意义,只有在特定环境下才能使用。
  • 以下划线开头的键是保留的(不是严格要求的)。

集合

集合就是 MongoDB 文档组,类似于 RDBMS 中的表。集合存在于数据库中,集合没有固定的结构。

集合的合法性:

  • 集合名不能是空字符串。
  • 集合名不能含有 \0 空字符,这个字符表示集合名的结尾。
  • 集合名不能以 system. 开头,这是为系统集合保留的前缀。
  • 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。
Capped Collections

Capped collections 就是固定大小的 collection,它是高性能自动的维护对象的插入顺序。它非常适合类似记录日志的功能和标准的 collection 不同,你必须要显式的创建一个capped collection,指定一个 collection 的大小,单位是字节。collection 的数据存储空间值提前分配的。

Capped collections 可以按照文档的插入顺序保存到集合中,而且这些文档在磁盘上存放位置也是按照插入顺序来保存的,所以当我们更新 Capped collections 中文档的时候,更新后的文档不可以超过之前文档的大小,这样话就可以确保所有文档在磁盘上的位置一直保持不变。

由于 Capped collection 是按照文档的插入顺序而不是使用索引确定插入位置,这样的话可以提高增添数据的效率。MongoDB 的操作日志文件 oplog.rs 就是利用 Capped Collection 来实现的。

要注意的是指定的存储大小包含了数据库的头信息。

db.createCollection("mycoll", {capped:true, size:100000})

元数据

在 MongoDB 数据库中命名空间 <dbname>.system.* 是包含多种系统信息的特殊集合(Collection),如下:

集合命名空间 描述
dbname.system.namespaces 列出所有名字空间。
dbname.system.indexes 列出所有索引。
dbname.system.profile 包含数据库概要(profile)信息。
dbname.system.users 列出所有可访问数据库的用户。
dbname.local.sources 包含复制对端(slave)的服务器信息和状态。

ObjectId

ObjectId 类似唯一主键,可以很快的去生成和排序,它包含 12 个字节,其含义如下:

  • 前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时。
  • 接下来的 3 个字节是机器标识码。
  • 紧接的两个字节由进程 id 组成 PID。
  • 最后三个字节是随机数。

使用方式:

var newObject = ObjectId()
newObject.getTimestamp()
newObject.str

时间与字符串

  1. BSON 字符串

    BSON 字符串都是 UTF-8 编码。

  2. 时间戳

    BSON 有一个特殊的时间戳类型用于 MongoDB 内部使用,与普通的日期类型不相关。

    在单个 mongod 实例中,时间戳值通常是唯一的。

    在复制集中,oplog 有一个 ts 字段。这个字段中的值使用 BSON 时间戳表示了操作时间。

    BSON 时间戳类型主要用于 MongoDB 内部使用。

  3. 日期

    表示当前距离 Unix 新纪元(1970年1月1日)的毫秒数。日期类型是有符号的,负数表示 1970 年之前的日期,创建及使用方式如下:

    var mydate = new Date()
    typeof mydate
    mydate.toString()
    Date()

    这样创建的时间是日期类型,可以使用 JS 中的 Date 类型的方法。

基本操作

数据库操作

  1. 创建连接

    # 连接实例:
    mongodb://root:123456@127.0.0.1/test

    更多 MongoDB 连接配置,参考:https://www.runoob.com/mongodb/mongodb-connections.html

  2. 创建或切换数据库

    # 如果不存在,则创建,如果已存在,则切换
    use mytest
    # 查看所有数据库(当创建的数据库没有数据时,不会显示)
    show dbs
    # 插入一条数据
    db.col1.insert({"name":"Chinmoku"})
    # 再次查询数据库列表
    show dbs
  3. 删除数据库

    use mytest
    db.dropDatabase()

集合与文档操作

  1. 创建集合

    # 创建集合
    db.createCollection("test")
    # 查看所有集合
    show collections
    # 查看所有集合(方式二)
    show tables
    # 创建固定集合
    db.createCollection("col2",{capped:true,autoIndexId:true,size:6142800,max:10000}) # 集合大小:6142800B,最大文档个数:10000

    在 MongoDB 中,其实不需要显式创建集合,在插入文档时,MongoDB 就会自动创建集合。

  2. 删除集合

    # db.collection.drop(),返回布尔值
    db.col2.drop()
  3. 文档基本操作

    # 插入文档
    db.col.insert({title: 'Telephone Book', user: 'Jason',mobile: '138****2573',site: 'https://www.jason.com',label: ['classmate', 'friend'],description: ''})
    db.col.find() # 查看文档内容
    # 也可以将数据定义为变量,然后插入该变量
    jason_document=({title: 'Telephone Book', user: 'Jason',mobile: '138****2573',site: 'https://www.jason.com',label: ['classmate', 'friend'],description: ''})
    db.col.insert(jason_document)
    
    # v3.2版本之后新增了insertOne和insertMany方法
    db.col2.insertOne({"name": "Lily"})
    db.col2.insertMany([{"name": "Charles"},{"name": "James"}])

    save 方法也可以创建文档。

  4. 更新文档

    # 参数(1.筛选条件,2.更新内容,3.***,4.***)
    db.col.update({'title':'Telephone Book'},{$set:{'title':'Telephone Book: Jason'}}) # 查询标题为Telephone Book的数据,并更新标题
    # 同时更新多条文档内容
    db.col.update({'user':'Jason'},{$set:{'title':'Telephone Book: Jason'}},{multi:true})
    
    # save(如果指定了 _id 字段,则更新对应文档,如果未指定,则新增文档)
    db.col.save({"_id" : ObjectId("61c9ad7bba2a467651726dba"),title: 'Telephone Book: Bill', user: 'Bill',mobile: '187****0966',site: 'https://www.bill.com',label: ['classmate'],description: ''})
    db.col.find() # 查询并格式化显示

    使用 save 方法更新文档时,文档内所有字段都会被更新,即 save 中未指定字段会被移除,save 指定但数据库不存在字段会被新增。

  5. 删除文档

    # 删除指定数据
    db.col.remove({'user':'Jason'})
    # 删除所有
    db.col.remove({})
  6. 查询文档

    # 查询所有
    db.col.find()
    # 查询并返回指定的键
    db.col.find({},{"title":1,_id:0})
    db.col.find()
    # 查询并格式化显示
    db.col.find().pretty()
    # 查询一条
    db.col.findOne()

    比较查询:

    操作 格式 示例
    等于 {<key>:<value>} db.col.find({“title”:“John”})
    小于 {<key>:{$lt:<value>}} db.col.find({“age”:{$lt:18}})
    小于等于 {<key>:{$lte:<value>}} db.col.find({“age”:{$lte:18}})
    大于 {<key>:{$gt:<value>}} db.col.find({“age”:{$gt:18}})
    大于等于 {<key>:{$gte:<value>}} db.col.find({“age”:{$gte:18}})
    不等于 {<key>:{$ne:<value>}} db.col.find({“age”:{$ne:18}})

    这些比较查询语句,类似于关系型数据库中的 select * from col where age > 18;

    AND 和 OR:

    # AND
    db.col.find({key1:value1, key2:value2})
    # OR
    db.col.find({$or:[{key1: value1},{key2:value2}]})
    # AND和OR联合使用
    db.col.find({"site": "https://www.jason.com", $or: [{"user": "Jason"},{"age": {$gt:18}}]})

进阶操作

$type

image-20211227211354948

使用示例:

# 查询title类型为string的数据
db.col.find({"title" : {$type : 2}})
# 等价于
db.col.find({"title" : {$type : 'string'}})

limit 和 skip

# 查询2条记录
db.col.find({},{"user":1,_id:0}).limit(2)
# 跳过1条数,并查询其后续的2条记录
db.col.find({},{"user":1,_id:0}).limit(2).skip(1)

排序

# 排序使用1和-1来分别表示正序和倒叙
db.col.find({},{"user":1,_id:0}).sort({"age":-1})

索引

# 根据键名user按顺序创建索引,1为升序,-1为降序
db.col.createIndex({"user":-1})
# 复合索引
db.col.createIndex({"title":1,"description":-1})
# 查看集合索引
db.col.getIndexes()
# 查看集合索引大小
db.col.totalIndexSize()
# 删除所有集合索引
db.col.dropIndexes()
# 删除集合指定索引
db.col.dropIndex("myindex")

创建索引还可以接受许多参数,如索引名称、是否唯一、设置过期时间等,详情可自行参考官方文档

聚合

db.mypost.aggregate([{$group : {_id : "$author", post_count : {$sum : 1}}}])
# 类似于如下SQL
select author, count(*) as post_count from mypost group by author
  1. 管道:

    在此处的示例中,$group 是 MongoDB 管道的一种,MongoDB 的聚合管道将 MongoDB 文档在一个管道处理完毕后将结果传递给下一个管道处理,并且管道操作是可以重复的。

    常用管道操作符:

    • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
    • matchmatch:用于过滤数据,只输出符合条件的文档。match 使用 MongoDB 的标准查询操作。
    • $limit:用来限制 MongoDB 聚合管道返回的文档数。
    • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
    • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
    • $group:将集合中的文档分组,可用于统计结果。
    • $sort:将输入文档排序后输出。
    • $geoNear:输出接近某一地理位置的有序文档。
  2. 表达式:

    此处示例中的 $sum 即是表示聚合操作的表达式。

    常用表达式:

    • $sum:计算总和。
    • $avg:计算平均值
    • $min:获取集合中所有文档对应值得最小值。
    • $max:获取集合中所有文档对应值得最大值。
    • $push:将值加入一个数组中,不会判断是否有重复的值。
    • $addToSet:将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。
    • $first:根据资源文档的排序获取第一个文档数据。
    • $last:根据资源文档的排序获取最后一个文档数据

复制(副本集)

MongoDB 复制是将数据同步在多个服务器的过程。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。复制还允许您从硬件故障和服务中断中恢复数据。

MongoDB 复制原理:

MongoDB 复制至少需要两个节点,其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。

主节点记录在其上的所有操作 oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

MongoDB 复制结构图如下所示:

image-20211227222410409

副本集具有如下特征:

  • N 个节点的集群
  • 任何节点可作为主节点
  • 所有写入操作都在主节点上
  • 自动故障转移
  • 自动恢复

副本集设置:

# 启动一个名为rs0的MongoDB实例
mongod --port 27017 --dbpath "A:\MongoDB\Server\5.0\data" --replSet rs0
# 客户端连接并启动一个新的副本集
rs.initiate()
# 查看副本集配置
rs.conf()
# 查看副本集状态
rs.status()

添加副本集成员:

# 副本集添加成员
rs.add("192.168.0.101:27017")
# 判断当前运行的Mongo服务是否为主节点
db.isMaster()

MongoDB 中你只能通过主节点将 Mongo 服务添加到副本集中,并且,MongoDB 的副本集与我们常见的主从有所不同,主从在主机宕机后所有服务将停止,而副本集在主机宕机后,副本会接管主节点成为主节点。

分片

MongoDB 中存在一种分片技术,它可以通过在多台机器上分割数据的方式,来使得数据库系统能存储和处理更多的数据或提高读写吞吐量。

分片结构集群部署图例:

image-20211227231337940

组件说明:

  • Shard:用于存储实际的数据块,实际生产环境中一个 shard server 角色可由几台机器组个一个 replica set 承担,防止主机单点故障。
  • Config Servers:mongod 实例,存储了整个 ClusterMetadata,其中包括 chunk 信息。
  • Query Routers:前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。

[❗️TODO]{.label .danger} 未完待补充

备份与恢复

# 数据备份,语法:mongodump -h <hostname><:port> -d dbname -o <path>
# 不指定参数,则连接本地27017端口服务,默认备份到目录 [mongodb]/bin/dump/ 中
mongodump
# 备份恢复,语法:mongorestore -h <hostname><:port> -d dbname <path>
# 不指定参数,则连接本地27017端口服务,默认从目录 [mongodb]/bin/dump/ 中获取备份文件
mongorestore

监控

mongostat 是 MongoDB 自带的状态检测工具,在命令行下使用。它会间隔固定时间获取 MongoDB 的当前运行状态,并输出。

使用方式:

mongostat # 无密码监控默认数据库
mongostat -uroot -p123456 --authenticationDatabase=admin

如提示 command serverStatus requires authentication,那么多半是因为 MongoDB 需要登录权限,详情可通过命令 mongostat --help 查看。

mongotop 也是 MongoDB 下的一个内置工具,mongotop 提供了一个方法,用来跟踪一个 MongoDB 的实例,查看哪些大量的时间花费在读取和写入数据。 mongotop 提供每个集合的水平的统计数据。默认情况下,mongotop 返回值的每一秒。

使用方式:

mongotop # 无密码监控默认数据库
mongotop -uroot -p123456 --authenticationDatabase=admin
mongotop -uroot -p123456 --authenticationDatabase=admin --lock # 监控各个数据库锁使用情况

API

Java API

此处 MongoDB Java API 以 SpringBoot 框架作为示例。

准备工作:

  1. 使用 SpringInitializer 创建一个测试项目。

  2. 配置依赖包。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  3. 启动文件配置:

    spring:
      data:
        mongodb:
          uri: "mongodb://localhost:27017/test"
          # 如未设置则不需要指定用户名及密码
          # username:
          # password: 
  4. 创建用于测试的实体类:

    @Data
    public class User {
        // id会被映射为mongodb文档的主键
        private String id;
        private String name;
        private int age;
    }

基础增删改查:

  1. 创建接口:

    public interface UserService {
    
        User insert(User user);
    
        User save(User user);
    
        User update(User user);
    
        List<User> findAll();
    
        boolean remove(User user);
    }
  2. 实现类:

    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private MongoTemplate mongoTemplate;
    
        @Override
        public User insert(User user) {
            return this.mongoTemplate.insert(user);
        }
    
        @Override
        public User save(User user) {
            return this.mongoTemplate.save(user);
        }
    
        @Override
        public User update(User user) {
            return this.mongoTemplate.save(user);
        }
    
        @Override
        public List<User> findAll() {
            return this.mongoTemplate.findAll(User.class);
        }
    
        @Override
        public boolean remove(User user) {
            DeleteResult result = this.mongoTemplate.remove(user);
            return result.getDeletedCount() > 0;
        }
    }
  3. 提供 API 访问:

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/list")
        public List<User> findAll() {
            return this.userService.findAll();
        }
    
        @PostMapping("/insert")
        public User insert(User user) {
            return this.userService.insert(user);
        }
    
        @PostMapping("/save")
        public User save(User user) {
            return this.userService.save(user);
        }
    
        @PostMapping("/update")
        public User update(User user) {
            return this.userService.update(user);
        }
    
        @PostMapping("/remove")
        public boolean remove(User user) {
            return this.userService.remove(user);
        }
    }
  4. 测试发送请求:

    ### 添加用户
    POST http://localhost:8080/insert?name=Lily&age=21
    Accept: application/json
    ###
    POST http://localhost:8080/insert?name=Mike&age=26
    Accept: application/json
    ###
    POST http://localhost:8080/insert?name=Jason&age=27
    Accept: application/json
    ###
    POST http://localhost:8080/insert?name=Philips&age=14
    Accept: application/json
    ### 添加
    POST http://localhost:8080/save?name=Jill&age=28
    Accept: application/json
    ### 修改
    POST http://localhost:8080/save?name=Jill&age=28&id=61caf27055120f6b62234917
    Accept: application/json
    ### 修改
    POST http://localhost:8080/update?name=Tom&age=33&id=61caf27055120f6b62234917
    Accept: application/json
    ### 查询列表
    GET http://localhost:8080/list
    Accept: application/json

扩展查询:

// 单一条件查询示例
@Override
public User queryById(String id) {
    Criteria criteria = Criteria.where("id").is(id);
    Query query = Query.query(criteria);
    return this.mongoTemplate.findOne(query, User.class); // 返回一条
}

// 符合条件查询示例
@Override
public List<User> queryByCondition(int age, String name) {
    Criteria criteria = new Criteria();
    criteria.orOperator(Criteria.where("age").gt(age), Criteria.where("name").is(name));
    Query query = Query.query(criteria);
    // 排序
    Sort sort = Sort.by(new Sort.Order(Sort.Direction.DESC, "id"));
    query.with(sort);
    return this.mongoTemplate.find(query, User.class); // 返回多条
}

// 分页查询
@Override
public List<User> queryWithPage(int index, int limit) {
    Query query = new Query();
    query.skip(index).limit(limit);// 从下标为index开始查询接下来的limit条数据
    return this.mongoTemplate.find(query, User.class);
}

// 模糊查询
@Override
public List<User> like(String chars) {
    Query query = new Query(Criteria.where("name").regex(chars));// 查询name中包含指定字符的数据
    return this.mongoTemplate.find(query, User.class);
}

// 查询并删除
@Override
public boolean queryToRemove(String id) {
    Query query = new Query(Criteria.where("id").is(id));
    DeleteResult result = this.mongoTemplate.remove(query);
    return result.getDeletedCount() > 0;
}

更多 Java API 用法,请查询 spring-boot-starter-data-mongodb 文档

Python API

  1. 安装 pymongo 连接工具:

    python3 -m pip3 install pymongo
    # 如需更新,执行如下命令:
    python3 -m pip3 install --upgrade pymongo
  2. 数据库与集合操作:

    #!/usr/bin/python3
     
    import pymongo
    
    # 建立连接
    myclient = pymongo.MongoClient("mongodb://localhost:27017/")
    # 创建数据库
    mydb = myclient["test"]
    # 查询数据库列表
    dblist = myclient.list_database_names()
    # dblist = myclient.database_names() 
    if "test" in dblist:
      print("数据库已存在!")
    
    # 创建集合
    mydb = myclient["test"]
    mycol = mydb["col"]
    # 查询集合列表
    collist = mydb.list_collection_names()
    if "col" in collist:
      print("集合已存在!")
  3. 增删改查

    新增:

    # 插入一条数据
    mydict = { "name": "Chinmoku", "site": "https://www.chinmoku.cc" }
    x = mycol.insert_one(mydict)
    print(x.inserted_id)  # 文档数据ID
    
    # 插入多条数据
    mylist = [
      { "name": "Facebook", "url": "https://www.facebook.com" },
      { "name": "知乎", "url": "https://www.zhihu.com" },
      { "name": "Github", "url": "https://www.github.com" }
    ]
    x = mycol.insert_many(mylist)
    print(x.inserted_ids)
    
    # 也可以指定ID
    mylist = [
      { "_id": 1, "name": "百度", "url": "https://www.baidu.com"},
      { "_id": 2, "name": "Google", "url": "https://www.google.com"}
    ]
    x = mycol.insert_many(mylist)
    print(x.inserted_ids)

    删除:

    # 删除单条
    myquery = { "name": "百度" }
    mycol.delete_one(myquery)
    
    # 删除多条(删除http协议网站)
    myquery = { "url": {"$regex": "^http:"} }
    x = mycol.delete_many(myquery)
    print("已删除文档个数:", x.deleted_count)
    
    # 删除所有文档
    x = mycol.delete_many({})
    
    # 删除集合
    mycol.drop()

    修改:

    # 修改单条
    myquery = { "id": "1" }
    newvalues = { "$set": { "name": "No 1" } }
    mycol.update_one(myquery, newvalues)
    
    # 修改多条
    myquery = { "url": {"$regex": "^http:"} }
    newvalues = { "$set": { "name": "非HTTPS协议网站" } }
    x = mycol.update_many(myquery, newvalues)
    print("已更新文档个数:", x.modified_count)

    查询:

    # 查询文档第一条数据
    x = mycol.find_one()
    
    # 查询文档所有数据
    for x in mycol.find():
      print(x)
    
    # 查询指定字段(指定1,表示需要返回的字段),注意,除_id外,其他字段不能0或1必须一致
    for x in mycol.find({},{ "_id": 0, "name": 1, "url": 1 }):
      print(x)
    
    # 反向返回字段(返回除url之外的其他字段)
    for x in mycol.find({},{ "url": 0 }):
      print(x)
    
    # 根据条件查询
    myquery = { "name": "Chinmoku" }
    mydoc = mycol.find(myquery)
    for x in mydoc:
      print(x)
    
    # 使用修饰符查询(比较name首字母)
    myquery = { "name": { "$gt": "H" } }
    mydoc = mycol.find(myquery)
    for x in mydoc:
      print(x)
    
    # 正则查询(查询name以C开头的数据)
    myquery = { "name": { "$regex": "^C" } }
    mydoc = mycol.find(myquery)
    for x in mydoc:
      print(x)
    
    # 返回指定条数
    myresult = mycol.find().limit(3)
    for x in myresult:
      print(x)

参考

本文相关代码已记录在 github,点击此处前往查看。本文内容如有错误之处,欢迎在评论区留言告知。

版权声明

本文链接:https://www.chinmoku.cc/java/database/mongodb-tutorial/

本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。

Press ESC to close