MongoDB学习笔记总结(含报错、问题、技巧)

环境

OS:Ubuntu20.04

MongoDB:v5.0.2

一.MongoDB简介

MongoDB是一个开源文档数据库,提供高性能,高可用性和自动扩展,旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

文档的数据结构和JSON基本一样。所有存储在集合中的数据都是BSON格式。

MongoDB中的记录是一个文档,它是由字段和值(key=>value)对组成的数据结构。 MongoDB文档与JSON对象相似。 字段的值可能包括其他文档,数组和文档数组。

在mongodb中基本的概念是文档(document)、集合(collection)、数据库(database)。

SQL术语/概念MongoDB术语/概念解释/说明
databasedatabase数据库
tablecollection数据库表/集合
rowdocument数据记录行/文档
columnField数据字段/域
indexIndex索引
table joins表连接,MongoDB不支持
primary keyprimary key主键,MongoDB自动将_id字段设置为主键

二.ubuntu安装MongoDB 5.0

1.导入MongoDB公共GPG Key(推荐去官网(官网地址:https://www.mongodb.com/)看最新的GPG Key)
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -

1

返回OK即操作成功,如果提示gnupg 未安装则先安装gnupg

sudo apt-get install gnupg
2.创建/etc/apt/sources.list.d/mongodb-org-5.0.list文件

不同ubuntu系统版本通过不同命令创建,可以查看当前ubuntu系统的版本:

lsb_release -dc

ubuntu20

echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list
3.更新apt-get
sudo apt-get update
4.安装MongoDB

安装指定版本(可去官网看最新的稳定版本并做安装):

sudo apt-get install -y mongodb-org=5.0.2 mongodb-org-database=5.0.2 mongodb-org-server=5.0.2 mongodb-org-shell=5.0.2 mongodb-org-mongos=5.0.2 mongodb-org-tools=5.0.2

安装最新版本(可能会下载到较老的版本,因为ubuntu源无更新的原因):

sudo apt-get install -y mongodb-org

查看MongoDB版本:首先切换到mongodb安装目录下–>输入命令:

./mongo --version
5.禁用自动升级,防止意外情况
echo "mongodb-org hold" | sudo dpkg --set-selections

echo "mongodb-org-database hold" | sudo dpkg --set-selections

echo "mongodb-org-server hold" | sudo dpkg --set-selections

echo "mongodb-org-shell hold" | sudo dpkg --set-selections

echo "mongodb-org-mongos hold" | sudo dpkg --set-selections

echo "mongodb-org-tools hold" | sudo dpkg --set-selections
6.启动运行

安装后会默认创建数据目录和日志目录如下:

默认数据目录:/var/lib/mongodb,默认日志目录:/var/log/mongodb

可以通过修改配置文件把数据和日志保存到其他目录(修改后需重启mongodb生效):

vim /etc/mongod.conf

2

mongodb默认是以mongodb这个用户运行的,如果修改了数据和日志目录需要创建对应的data和log目录并赋予用户mongodb对应的权限,没有data和log目录或者权限不够时启动会失败

sudo chown -R mongodb /home/hadoop/mongodb

启动mongodb:

sudo systemctl start mongod

停止mongodb:

sudo systemctl stop mongod

重启mongodb:

sudo systemctl restart mongod

查看是否启动成功:

sudo systemctl status mongod

启动成功的显示

3

启动失败的显示:

4

7.卸载MongoDB

停止服务:

sudo systemctl stop mongod 或 sudo service mongod stop

移除安装包:

sudo apt-get purge mongodb-org*

移除数据和日志目录(以下为默认安装目录,需要修改成自己配置的实际目录):

sudo rm -r /var/log/mongodb

sudo rm -r /var/lib/mongodb
8.进入MongoDB shell命令模式
mongo

或指定端口

mongo -host 127.0.0.1:27017

5

进入shell界面就可以查看数据库和创建数据库、集合等等

6

问题总结
问题1:在更新apt-get(sudo apt-get update)时大部分包都忽略或错误

解决方法一:(容易)

1、找到设置 2、软件和更新 3、选择其他站点 4、选择最佳服务器 5、重新在终端执行命令。(以下为在ubuntu20,ubuntu16在桌面右上角可找到设置及软件和更新)

11

12

13

等进度条完成之后

14

15

16

然后重新在终端执行命令即可。

解决办法二:换源

如果还不行的话,可以尝试手动换源(推荐清华源和阿里云源)

阿里巴巴开源镜像站:https://developer.aliyun.com/mirror/

17

18

换源步骤:(这里以阿里云源为例子)

首先备份源列表

sudo cp /etc/apt/sources.list /etc/apt/sources.list_backup

打开sources.list文件修改,在文件最前面添加阿里云镜像源:

sudo vim /etc/apt/sources.list

在文件的最前面加入:(按红圈中标志复制全部)

19

刷新列表

sudo apt-get update

sudo apt-get upgrade

sudo apt-get install build-essential

还有可能是Ubuntu版本较老的原因,因为ubuntu16已经不维护了,可考虑省级ubuntu18和20,

但记得做好快照,以防万一发生意外可以恢复原状。

问题2:

20

或者

21

**原因:**文件权限问题,用户mongod没有对必需文件的写权限,导致数据库服务不能启动。

解决办法:

sudo systemctl stop mongod

sudo chown -R mongodb /home/hadoop/mongodb   (赋予权限)

sudo systemctl start mongod

即可解决

如果操作完还是解决不了,可尝试重启linux,再启动mongodb

reboot
sudo systemctl start mongod
问题3:

如果出现报错:Failed to start mongod.service: Unit mongod.service not found.

解决方法:

  • 首先执行:

    sudo systemctl daemon-reload
    
  • 然后再启动,还是报错可以按以下步骤继续处理

sudo vim /etc/systemd/system/mongodb.service

添加以下内容保存后再重启mongodb

[Unit]

Description=High-performance, schema-free document-oriented database

After=network.target

 

[Service]

User=mongodb

ExecStart=/usr/bin/mongod --quiet --config /etc/mongod.conf

 

[Install]

WantedBy=multi-user.target
问题4:

执行apt install/update时出现E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable)

解决方法:

首先确认是否有更新任务在运行,如果有等待其他更新任务完成或结束更新任务,否则执行以下命令:

sudo rm /var/lib/dpkg/lock
sudo dpkg --configure -a
sudo apt update

三.方法的名称、参数说明、作用、使用时的注意事项

首先进入mongodb shell

启动mongodb:

sudo systemctl start mongod

查看是否启动成功:

sudo systemctl status mongod

进入mongodb shell:

mongo
MongoDB数据库、集合与文档的基本操作

MongoDB中的记录是一个文档,它是由字段和值(key=>value)对组成的数据结构。 MongoDB文档与JSON对象相似。 字段的值可能包括其他文档,数组和文档数组。

75

1.特点
  • 文档中的键值对是有序的:
    {“sport”:“football”,“address”:“北京”,“phone”:“13989622814”} {“sport”:“football”,“phone”:“13989622814”,“address”:“北京”}
  • 值区分字符串和数字:
    {“name”:“json”,“age”:“18”}
    {“name”:“json”,“age”:18 }
  • 键区分大小写:
    {“name”:“json”,“age”:18}
    {“Name”:“json”,“age”:18}
2.文档键(field)命名规则

文档键(field)的命名需要注意以下几点:

  • _id是系统保留的关键字。
  • 不能包含\0或空字符。
  • 不能以$开头。
  • 不能包含.(点号)。
  • 区分大小写的且不能重复(同一个文档中)
3.基本数据类型
  • null:表示空值或者不存在的字段。
    {“x”:null }
  • 布尔(boolean):“true”和“false”
    {“x”:true }
  • 数字:MongoDB支持的数字类型比较广泛,包括32位整数、64位整数、64位浮点数。
    {“x”:3.14}
  • 字符串:UTF-8字符串都可以表示为字符串类型的数据:
    {“x”:“HelloWorld!”}
  • 数组:值的集合或者列表可以表示成数组:
    {“x”:[“a”,“b”,“c”] }
  • 对象:对象Object:
    {“x”: Object()}
4._id和ObjectId
  • 自动生成_id:如果插入文档的时候没有指定“_id”值,系统会自动帮你创建一个,每个文档都有唯一的“id”值。
  • _ObjectId:ObjectId是“_id”的默认类型。
5.日期

MongoDB中支持Date作为键的一个值

例如:{“name”:“jack”,“date”:new Date()}

文档中,date属性值为:new Date()
new Date() 创建了一个Date对象,返回日期的字符串表示。

6.内嵌文档

​ 就是把整个MongoDB文档当做另一个文档中一个键的一个值。

{
	“name”:“jack”,
	“address”:{
			“street”:“Zoo Park Street”,
			“city”:“Landon”,
                          }
}

进入mongodb shell

执行

mongo

mongosh

命令进入mongodb shell(注意这里mongod的服务已经启动)

MongoDB 创建数据库的语法格式如下:

use "DATABASE_NAME"

如果数据库不存在,则创建数据库,否则切换到指定数据库。

例如:以下示例创建了数据库 Employee:

use Employee

23

查看所有数据库:

show dbs

24

刚创建的数据库 Employee 并不在数据库的列表中, 要显示它,需要向 Employee 数据库插入一些数据。

db.Employee.insert({"name":"google"})

show dbs

25

MongoDB 中默认的数据库为 test,如果没有创建新的数据库,集合将存放在 test 数据库中。

7.统计数据库信息
db.stats()	

{
“db” : “test”, //数据库名
“collections” : 0, //集合数量
“objects” : 0, //文档数量
“avgObjSize” : 0, //平均每个文档的大小
“dataSize” : 0, //数据占用空间大小,不包括索引,单位为字节
“storageSize” : 0, //分配的存储空间
….
}

MongoDB 删除数据库

MongoDB 删除数据库的语法格式如下:

db.dropDatabase()

删除当前数据库,默认为 test,可以使用 db 命令查看当前数据库名。

db

首先,使用show dbs查看所有数据库,然后用use切换到数据库Employee,再进行删除操作,最后使用show查看数据库是否删除成功,操作步骤如图所示:

show dbs

use Employee

db.dropDatabase()

show dbs

26

8.集合操作

命令格式:

db.createCollection(name, options)

例如在myDB数据库下创建myCollection集合,

执行以下命令:

use myDB

db.createCollection("myCollection")

查询数据库中所有的集合使用:

show collections

27

对集合重命名使用renameCollection方法,如下图所示:

db.myCollection.renameCollection("myColl")

查看集合详细信息

db.getCollectionInfos()

[
{
“name” : “myColl”,
“type” : “collection”,
“options” : {

	},
	"info" : {
		"readOnly" : false
	},
	"idIndex" : {
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "myDB.myColl"
	}
}

]

删除集合使用drop方法,如下图所示:

db.myColl.drop()

show collections

27

定长集合

db.createCollection( "myCollection",{ capped:true,size:10000 } )

capped:是否定长,true为定长
size:集合中文档数量大小

定长集合中容量循环使用,用于实时监控

db.myCollection.isCapped()                //判断集合是否定长。

插入文档

MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:

db.COLLECTION_NAME.insert(document)

示例:向数据库student的stuinfo集合插入以下数据

28

  • 使用数据库student
use student
  • insert方法插入单个文档
db.stuinfo.insert({_id:001,name:'alice',age:18})
  • save方法插入单个文档
db.stuinfo.save({_id:002,name:'nancy',age:19})
  • 插入多个文档
db.stuinfo.insert([{_id:003,name:'harry',age:18},{_id:004,name:'curry',age:19}])

文档插入完成后,使用find()查看集合数据,具体操作步骤如下图所示:

29

注意: _id和ObjectId

  • id 是文档的唯一标示,
  • _ObjectId是_id的缺省产生办法
例:
db.myCollection.insert({"x":"10"})    //不指定_id的值,自动创建
db.myCollection.insert({"_id":"user001","y":"10"})    //指定_id的值,取指定值
db.myCollection.find()
查询结果:
{ "_id" : ObjectId("5c5ff402eb5725b5d8961b45"), "x" : "10" }
{ "_id" : "user001", "y" : "10" }

以上示例中stuinfo是我们的集合名,如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档。

还可以可以先将文档定义为一个变量,再进行插入:

s={_id:5,name:'张三',age:19}

db.stuinfo.insert(s)

操作结果如下图所示:

30

插入多条nancy的记录:

db.stuinfo.insert([{_id:006,name:'nancy',age:17},{_id:007,name:'nancy',age:21}])

31

9.固定集合创建与删除
  • MongoDB中有一种特殊类型的集合,值得我们特别留意,那就是固定集合(capped collection)。

  • 固定集合可以声明collection的容量大小,其行为类似于循环队列。数据插入时,新文档会被插入到队列的末尾,如果队列已经被占满,那么最老的文档会被之后插入的文档覆盖。

  • 固定集合特性:固定集合很像环形队列,如果空间不足,最早的文档就会被删除,为新的文档腾出空间。一般来说,固定集合适用于任何想要自动淘汰过期属性的场景。

  • 固定集合应用场景:比如日志文件,聊天记录,通话信息记录等只需保留最近某段时间内的应用场景,都会使用到MongoDB的固定集合。

  • 固定集合的优点:

    1.写入速度提升。固定集合中的数据被顺序写入磁盘上的固定空间,所以,不会因为其他集合的一些随机性的写操作而“中断”,其写入速度非常快(不建立索引,性能更好)。
    2.固定集合会自动覆盖掉最老的文档,因此不需要再配置额外的工作来进行旧文档删除。设置Job进行旧文档的定时删除容易形成性能的压力毛刺。
    固定集合非常实用与记录日志等场景。

  • 固定集合的创建:不同于普通集合,固定集合必须在使用前显式创建。
    例如,创建固定集合coll_testcapped,大小限制为1024个字节。

    db.createCollection("coll_testcapped",{capped:true,size:1024});
    
  • 固定集合的创建:除了大小,创建时还可以指定固定集合中文档的数据量。
    例如,创建固定集合coll_testcapped,大小限制为1024个字节,文档数量限制为100。

    db.createCollection("coll_testcapped2",{capped:true,size:1024,max:100});
    
  • 注意事项:

    1.固定集合创建之后就不可以改变,只能将其删除重建。
    2.普通集合可以使用convertToCapped转换固定集合,但是固定集合不可以转换为普通集合。
    3.创建固定集合,为固定集合指定文档数量限制时(指参数max),必须同时指定固定集合的大小(指参数size)。不管先达到哪一个限制,之后插入的新文档都会把最老的文档移除集合。

    4.使用convertToCapped命令将普通集合转换固定集合时,既有的索引会丢失,需要手动创建。并且,此转换命令没有限制文档数量的参数(即没有max的参数选项)。

    5.不可以对 固定集合 进行分片。
    6.对固定集合中的文档可以进行更新(update)操作,但更新不能导致文档的Size增长或缩小,否则更新失败。
    假如集合中有一个key,其value 对应的数据长度为100个字节,如果要更新这个key 对应的value,更新后的值也必须为100个字节,大于100个字节不可以,小于100个字节也不可以。

    7.不可以对固定集合执行删除文档操作,但可以删除整个集合。
    8.还有一定需要注意,对集合估算size时,不要依据集合的storageSize ,而是依据集合的size。storageSize是wiredTiger存储引擎采用高压缩算法压缩后的。

更新文档(区分update和save的区别)

MongoDB 使用 update() 和 save() 方法来更新集合中的文档。

  • update() 方法用于更新已存在的文档。语法格式如下:
db.collection.update(<criteria>,<objNew>,upsert,multi,writeConcern)

参数说明:

query : update的查询条件,类似sql update查询内where后面的。

objNew : update的对象和一些更新的操作符(如 , , ,inc…)等,也可以理解为sql update查询内set后面的

upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。

multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

writeConcern :可选,抛出异常的级别。

示例

通过 update() 方法来更新上述文档的姓名(name): 将姓名(name)为curry的文档 更新为了 “李四”。

执行命令:

db.stuinfo.update({name:'curry'},{$set:{name:'李四'}})

通过find()查看修改是否成功,具体操作步骤如下所示:

32

以上语句只会修改第一条发现的文档,如果要修改多条相同的文档,则需要设置 multi 参数为 true。

如将name为“nancy”所有文档更新为“王五”

db.stuinfo.update({name:'nancy'},{$set:{name:'王五'}},false,true)

操作结果如下图所示:

33

  • save() 方法通过传入的文档来替换已有文档。语法格式如下:
db.collection.save(<document>,{writeConcern:<document>})

参数说明:

document : 文档数据。

writeConcern :可选,抛出异常的级别。

该方法在4.2版本开始已废弃

示例:替换 _id 为2的文档的数据:

db.stuinfo.save({_id:2,name:'curry',age:20})

操作步骤如下图所示:

34

10.更新文档——文档替换

用一个新文档代替匹配的文档

{ 
	"_id" :ObjectId("5c6005ea0fc42acdb75f74a6"),
 	"name" : "foo",
	 "nickname" : "bar", 
	"friends" : 12, 
	"enemies" : 2
 }

变成

{
	"_id" : ObjectId("5c6005ea0fc42acdb75f74a6"),
	"nickname" : "bar",
	"relations" : {
		"friends" : 12,
		"enemies" : 2
	},
	"username" : "foo"
}

修改步骤:

> var u=db.user.findOne({"name":"foo"})                //将要修改的文档存到对象u中
> u.relations={"friends":u.friends,"enemies":u.enemies}//对象u中新增字段relations
{ "friends" : 12, "enemies" : 2 }                      //relations嵌套文档
> u.username=u.name
foo
> delete u.friends
true
> delete u.enemies
true
> delete u.name
true
> db.user.update({"name":"foo"},u)                     //更新文档
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

11.更新文档——修改器
  • $inc (增加和减少,只能针对数字类型)
插入一个文档
> db.myColl.insert({title:"first",visites:107})
> db.myColl.find()
{ "_id" : ObjectId("5c887166713719923acc4c92"), "title" : "first", "visites" : 107}
修改参观次数增加1
> db.myColl.update({title:"first"},{$inc:{visites:1}})
> db.myColl.find()
{ "_id" : ObjectId("5c887166713719923acc4c92"), "title" : "first", "visites" : 108}
修改参观次数减少2
> db.myColl.update({title:"first"},{$inc:{visites:-2}})
> db.myColl.find()
{ "_id" : ObjectId("5c887166713719923acc4c92"), "title" : "first", "visites" : 106}
  • $set (可以完成特定需求的修改)
> db.author.findOne()
{
	"_id" : ObjectId("5c600dbb0fc42acdb75f74a7"),
	"name" : "foo",
	"age" : 20,
	"gender" : "male",
	"intro" : "student"
}

使用$set进行修改:

> db.author.update({"name":"foo"},{$set:{"intro":"teacher"}})
> db.author.findOne()
{
	"_id" : ObjectId("5c600dbb0fc42acdb75f74a7"),
	"name" : "foo",
	"age" : 20,
	"gender" : "male",
	"intro" : "teacher"
}

$set不仅仅可以修改变量,还可以修改数据类型:

db.author.update({name:"foo"},{$set:{intro:["teacher","programmer"]}}) //String变成了数组
  • $push修改器 (可以完成数组的插入)
原数据:
{
        "_id" : ObjectId("5a1656e656d8db3756cafce8"),
        "title" :"a blog",
        "content" :"...",
        "author" : "foo"
}
使用$push插入数组:
db.posts.update({title:"a blog"},
    {$push:{comments:{name:"leon",email:"leon.email.com",content:"leon replay"}}})
修改结果:
{ "_id" :ObjectId("5a1656e656d8db3756cafce8"),        "title" : "a blog", 
"content" :"...",
"author" : "foo",
"comments" : [ { "name" : "leon",
	"email" : "leon.email.com", 
	"content" : "leon replay"}]
 }
  • $ addToSet修改器
原数据:
{ 
"_id" : ObjectId("5a1659a756d8db3756cafce9"),
"name" : "foo",
"age" :12, 
"email" :[foo@example.com,foo@163.com]
}
向email数组中添加一个email信息:
Db.user.update({name:“foo”},{$addToSet:{Email:“foo@qq.com”}})
修改结果:
{ 
"_id" : ObjectId("5a1659a756d8db3756cafce9"),
"name" : "foo",
"age" :12, 
"email" :[foo@example.com,foo@163.com,		    foo@qq.com] 
}

例子:

原数据:
    { "_id" :ObjectId("59f00d4a2844ff254a1b68f7"), "x" :1 }
{ "_id" :ObjectId("59f00d4a2844ff254a1b68f8"), "x" : 1 }
{ "_id" :ObjectId("59f00d4a2844ff254a1b68f9"), "x" : 1 }
{ "_id" :ObjectId("59f00d4a2844ff254a1b68fa"),"x" :2 }

要求:把所有x为1的数据改为99

采用命令:
db.集合名.update({x:1},{$set:{x:99}},{multi: true})

修改结果:
{ "_id" :ObjectId("59f00d4a2844ff254a1b68f7"),"x" :99 }
{ "_id" :ObjectId("59f00d4a2844ff254a1b68f8"), "x" :99 }
{ "_id" :ObjectId("59f00d4a2844ff254a1b68f9"), "x" :99 }
{ "_id" :ObjectId("59f00d4a2844ff254a1b68fa"),"x" : 2 }

更多示例:

生成多条记录:

for(var i=1;i<10;i++)  db.col.insert({count:i,test2:false,test5:true})

35

36

更新按条件查出来的第一条记录:

db.col.update( { "count" : { $gt : 1 } } , { $set : { "test2" : "OK"} } );

37

更新按条件查出来的全部条记录:

db.col.update( { "count" : { $gt : 3 } } , { $set : { "test2" : "OK"} },false,true );

38

不存在update的记录,会添加一条:

db.col.update( { "count" : { $gt : 14 } } , { $set : { "test5" : "OK"} },true,false );

39

更新按条件查出来的全部条记录:

db.col.update( { "count" : { $gt : 1 } } , { $inc : { "count" : 1} },false,true );

40

更新按条件查出来的第一条记录:

db.col.update( { "count" : { $gt : 10 } } , { $inc : { "count" : 1} },false,false );

41

删除文档(注意区分remove和delete)

remove(),deleteOne() 和 deleteMany() 方法可以用来移除集合中的数据。

  • 删除集合col下全部文档
db.col.deleteMany({})

db.col.remove({})

具体如下图所示

42

  • 删除指定条件的文档

删除集合stuinfo中name等于王五的全部文档:

db.stuinfo.deleteMany({name:'王五'})

删除 age等于 18 的一个文档:

db.stuinfo.deleteOne({age:18})

步骤如图:

43

44

四.MongoDB数据库查询与聚合操作

1.使用 find() 方法进行文档基本查询

语法格式如下:

db.collection.find(query, projection)

参数说明:

query :可选,使用查询操作符指定查询条件

projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。指定键的值为0,不返回该键值对;为1时返回。

2.文档查询条件的使用

76

3.特定类型查询

针对特定类型的文档进行查询,如查询键为NULL的空文档

4.聚合查询

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。db.collection.aggregate()是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。

77

1.mongo查询

1.启动mongo

Mongo

2.使用test数据库,并新建items集合,保存订单相关信息。

use test
db.createCollection("items")

45

3.插入文档数据:

每个文档对应订单中某个商品相关信息,包括:

pnumber:商品编号

quantity:商品数量

price:商品单价

插入如下商品信息:

db.items.insert([

{"quantity":2,price:5.0,pnumber:"p003"},

{quantity:2,price:8.0,pnumber:"p002"},

{quantity:1,price:4.0,pnumber:"p002"},

{quantity:2,price:4.0,pnumber:"p001"},

{"quantity":4,price:10.0,pnumber:"p003"},

{quantity:10,price:20.0,pnumber:"p001"},

{quantity:10,price:20.0,pnumber:"p003"},

{quantity:5,price:10.0,pnumber:"p002"}

])

46

4.查询插入结果:

pretty() 方法以格式化的方式来显示所有文档

db.items.find().pretty() 

47

空的查询文档{}会匹配集合的全部内容。若是不指定查询文档,默认就是{}。例如:

db.user.find({})                         //即查询user集合中的全部内容
db.user.find({“age”:18})                //查找所有的“age”的值为18的文档
db.user.find({“name”:“jack”})           //想匹配一个字符串,“name”的值为“jack”的
db.user.find({“name”:“jack”,“age”:18})//查询所有用户名为“jack”且年龄为18的用户

5.统计items共有多少个文档数据

db.items.count()

48

6.查询价格大于5的商品数据

大于使用gt操作符,另外操作符前面要带上$符号

db.items.find({price:{$gt:5}})

49

7.多条件查询

例:查询quantity为10且价格大于等于5的商品数据

db.items.find({quantity:10,price:{$gte:5}})

50

8.使用or来进行条件查询,格式如下:

db.col.find({$or:[{key1: value1}, {key2:value2}]})

例:查询quantity为10或价格大于等于5的商品数据

db.items.find({$or:[{quantity:10},{price:{$gte:5}}]})

51

9.AND 和 OR 联合使用

例:查询pnumber为“p003”且quantity为10或价格大于等于5的商品数据

db.items.find({pnumber:"p003",$or:[{quantity:10},{price:{$gte:5}}]})

52

10.查询条件——包含( i n ) 或 不 包 含 ( in)或不包含( innin)

例:

  • 查询国籍是中国或者美国的学生信息

    db.persons.find({country:{$in:[“USA”,“China”]}})
    
  • 查询国籍不是中国或美国的学生信息。

    db.persons.find({country:{$nin:[“USA”,“China”]}})
    

11.查询条件——” o r ” 查 询 与 “ or”查询与“ ornot”查询

$or 查询
查询语文成绩大于90或者英语成绩大于85的学生信息:

db.personsr.find({$or:[{c:{$gte:85}},{e:{$gte:90}}]})

$not 查询
查询出名字中不存在“foo”的学生的信息:

db.persons.find({name:{$not:/foo/}})

12.特定类型查询——查询数组

数据:db.food.insert({"fruit":["apple","banana","peach"]})

接下来是具体的查询:

每一个元素都是整个键(数组的键)的值
db.food.find({fruit:"banana"})
$all查询:需要多个元素来匹配数组时
如:要找到既有“apple”又有“banana”的文档
db.food.find({fruit:{$all:["banana","apple"]}}) 
使用key.index语法指定数组的下标进行精准查询
db.food.find({"fruit.2":"peach"})
$size查询:查询指定数组的大小的文档
db.food.find({"fruit":{"$size":3}})

查询结果都为:

{
    "_id" :ObjectId("5b1dd90d1f23e9c34fc030a8"),
      "fruit" :[ "apple","banana", "peach"]
    }

13.游标

db.collection.find()方法返回一个游标,对于文档的访问,我们需要进行游标迭代。

游标使用过程如下:

  • 声明游标:

    var cursor =  db.collectioName.find({query},{projection});
    
  • 打开游标:

cursor.hasNext() //判断游标是否已经取到尽头。
  • 读取数据:

    cursor.Next()    //取出游标的下一个文档。
    
  • 关闭游标:

    cursor.close()   //此步骤可省略,通常为自动关闭,也可以显示关闭。
    
    //我们用while循环来遍历游标示例:
    var mycursor = db.user.find({})
    while(mycursor.hasNext()) {
     	printjson(mycursor.next());
     }
    
  • 游标——输出结果集

    db.collection.find()方法返回一个游标,对于文档的访问,我们需要进行游标迭代。

    1.使用print输出游标结果集:

    var cursor = db.user.find()
    while (cursor.hasNext()) {
    	print(tojson(cursor.next()))
    }
    

    2.使用printjson输出游标结果集:

    var cursor = db.user.find()
    while (cursor.hasNext()) {
    	printjson(cursor.next()))
    }
    
  • 游标——迭代

    1.迭代函数:游标有一个迭代函数允许我们自定义回调函数来逐个处理每个单元。 cursor.forEach(回调函数),步骤如下:定义回调函数、打开游标、迭代。

    数据源:

    { "_id" :ObjectId("5b1dd90d1f23e9c34fc030a8"), "fruit" :[ "apple","banana","peach" ] }
    { "_id" :ObjectId("5b1dddc51f23e9c34fc030a9"), "fruit" :[ "apple","kumquat","orange" ] }
    { "_id" :ObjectId("5b1ddddc1f23e9c34fc030aa"), "fruit" :[ "cherry","banana","apple" ] }
    
    • 先定义一个函数(获取文档fruit数组)

      var getFruit=function(obj){ print(obj.fruit)}
      
    • 打开游标:

      var cursor=db.food.find();
      
    • 迭代:

      cursor.forEach(getFruit);
      

    输出结果:

    apple,banana,peach
    apple,kumquat,orange
    cherry,banana,apple
    

    2.基于数组迭代

    数据源:

    { "_id" :ObjectId("5b1dd90d1f23e9c34fc030a8"), "fruit" :[ "apple","banana","peach" ] }
    { "_id" :ObjectId("5b1dddc51f23e9c34fc030a9"), "fruit" :[ "apple","kumquat","orange" ] }
    { "_id" :ObjectId("5b1ddddc1f23e9c34fc030aa"), "fruit" :[ "cherry","banana","apple" ] }
    

    具体操作:

    var cursor=db.food.find();
    var documentArray =cursor.toArray();
    printjson (documentArray);
    

    输出结果:

    [{ "_id" :ObjectId("5b1dd90d1f23e9c34fc030a8"), "fruit" :[ "apple","banana","peach" ] }
    { "_id" :ObjectId("5b1dddc51f23e9c34fc030a9"), "fruit" :[ "apple","kumquat","orange" ] }
    { "_id" :ObjectId("5b1ddddc1f23e9c34fc030aa"), "fruit" :[ "cherry","banana","apple" ] }]
    

14.使用聚合aggregate

MongoDB 中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。

语法:

db.collection.aggregate(pipeline, options)

例:

db.orders.aggregate([
   { $match: { status: "A" } },
   { $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])

第一阶段: m a t c h 阶 段 , 过 滤 查 询 结 果 第 二 阶 段 : match阶段,过滤查询结果 第二阶段: matchgroup阶段,对文档进行分组计算

81

82

例:统计订单中所有商品的数量,即统计quantity的总和。

db.items.aggregate([{$group:{_id:null,total:{$sum:"$quantity"}}}])

53

例:通过产品类型来进行分组,然后在统计卖出的数量

db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}}])

54

例:通过相同的产品类型来进行分组,然后查询相同产品类型卖出最多的订单详情。

db.items.aggregate([{$group:{_id:"$pnumber",max:{$max:"$quantity"}}}])

55

例:通过相同的产品类型来进行分组,然后查询每个订单详情相同产品类型卖出的平均价格

db.items.aggregate([{$group:{_id:"$pnumber",price:{$avg:"$price"}}}])

56

15.管道的使用

例:通过相同的产品类型来进行分组,统计各个产品数量,然后获取最大的数量。

db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}},{$group:{_id:null,max:{$max:"$total"}}}])

57

2.索引

提高查询效率最有效的手段。是解决查询速度缓慢而推出的一种特殊的数据结构,以易于遍历的形式存储部分数据内容;索引数据存储在内存当中,同样加快了索引查找数据的效率。

索引特点:

  • 通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录
  • 可以加快查询,但是同时降低了修改插入等性能
  • 是特殊的数据结构,索引是对数据库表中一列或多列的值进行排序的一种结构
  • 默认是用btree来组织索引文件

创建索引:

命令格式:

db.collection.createIndex( <keys>,<options> )

例:按age字段创建升序索引:

db.person.createIndex({age:1})

keys:希望创建索引的名称及排序方式,1 代表按升序排列;-1 代表按降序排列。
options:可选参数,表示建立索引的设置。可选值如下:

ParameterTypeDescription
backgroundBoolean在后台建立索引,以便建立索引时不阻止其他数据库活动,默认值为false。
uniqueBoolean创建唯一索引,默认值 false
namestring索引的名称。假设未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
partialFilterExpressionBoolean如果指定,MongoDB只会给满足过滤表达式的记录建立索引。
sparseBoolean对文档中不存在的字段数据不启用索引,默认值是 false。
expireAfterSecondsinteger指定索引的过期时间
storageEnginedocument允许用户配置索引的存储引擎

索引的类型

  • 默认索引

    对于每一个集合,默认会在_id字段上创建索引,而且这个特别的索引不能删除。_id字段是强制唯一的,由数据库维护。

  • 单键索引

    在一个键上创建的索引就是单键索引,单键索引是最常见的索引,如MongoDB默认创建的_id的索引就是单键索引

    db.collection.createIndex(key, options)
    
    db.getCollection('test').createIndex( {“name”:1} )
    
    db.getCollection('test').createIndex( {“name”:-1} )
    ensureIndex()
    

    79

    在后台创建索引:

    db.values.createIndex({open: 1, close: 1}, {background: true})
    
  • 复合索引

    在多个键上建立的索引就是复合索引

    db.getCollection('test').createIndex( {“name”:1,”phone”:-1} )
    

    name是正序排列,phone是逆序排列的。

    //find操作
    db.getCollection('test').find({name:"qiiq"})
    
    db.getCollection('test').find({name:"qiiq",phone:12512135})
    //这两种操作是能走联合索引。
    
    
    //下面两种操作时不能走联合索引
    db.getCollection('test').find({phone:12512135,name:"qiiq"})
    
    db.getCollection('test').find({phone:12512135})
    
  • 多key索引/多键索引(Multikey Index)

    如果文档中含有array类型字段,可以直接对其名称建立索引,这样MongoDB就会为内嵌数组中的每个元素建立一个独立的索引

    注意:多键索引不等于在多列字段上创建索引(复合索引)

  • 复合多键索引

    对于一个复合多键索引,每个索引最多可以包含一个数组。
    在多于一个数组的情形下来创建复合多键索引不被支持。

    假定存在如下集合

    { _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" }
    

    创建索引db.COLLECTION_NAME.createIndex({a:1,b:1})是不允许的,因为a和b都是数组。

  • 文本索引

  • 地理位置索引

  • 哈希索引

删除索引

主要有两种方式:
1.会删除当前集合中的所有索引(_id上的默认索引除外)。

db.集合名.dropIndexes()

2.可以根据指定的索引名称或索引文档删除索引(_id上的默认索引除外)。

db.集合名.dropIndex(index)

例:删除刚才创建的age索引:

db.person.dropIndex({age:1})
唯一索引

唯一索引可以确保集合的每一个文档的指定键都有唯一值。
语法如下:

db.collection.createIndex(索引名称或索引文档, {unique:true});

例如,如果想保住文档的“name”键都有不一样的值,创建一个唯一索引就好了:

db.people.createIndex({“name”:1},{“unique”:true})

即:people集合中创建按name键的值升序排列的唯一索引

  • 消除重复
    例如,删除“name”键重复的索引值:

    db.people.createIndex({“name”:1},{“unique”:true,“dropDups”:true}})
    
  • 复合唯一索引

    复合索引:就是建立在多个字段上的索引。例如:

    db.person.createIndex({“age”:1,“name”:1})
    

    复合唯一索引:

    db.person.createIndex({“age”:1,“name”:1},{"unique":true})
    
索引管理
  • 查询索引
    查询索引大小:

    db.集合名.totalIndexSize();(即索引所占空间大小)
    

    如:80

  • 修改索引
    Mongodb没有单独的修改索引的方法,如果需要修改某个索引,需要先删除旧有的索引,再创建新的索引。

    db.集合名.dropIndex("索引名称")
    

五.python程序导入数据(python操作MongoDB)

pymongo是python访问MongoDB的模块,该模块定义了一个操作MongoDB的类PyMongoClient,包含了连接管理、集合管理、索引管理、增删改查、文件操作、聚合操作等方法。

1.python操作MongoDB例1:

数据准备

1.新建python文件

vim pydtf.py

58

2.导入数据

  • 编写python程序导入数据至数据库淘宝,集合为order_info
from pymongo import MongoClient
from random import randint
import datetime

client = MongoClient('localhost',27017) # 建立连接
db = client.taobao # 连接taobao数据库
order = db.order_info # 设置集合
# 设置文档内容列表
status = ['A','B','C'] 
cust_id = ['A123','B123','C123']
price = [500,200,250,300]
sku = ['mmm','nnn']
# 循环生成随机数据
for i in range(1,100):
    items = []
    item_count =randint(2,6)
    for n in range(item_count):
        items.append({"sku":sku[randint(0,1)],"qty":randint(1,10),"price":randint(0,5)})
        # 生成新记录
    new = {
    "status":status[randint(0,2)],
    "cust_id":cust_id[randint(0,2)],
    "price":price[randint(0,3)],
    "ord_date":datetime.datetime.utcnow(),
    "items":items
    }
    print(new)
    # 插入到数据库
    order.insert_one(new)
print(order.estimated_document_count())

59

  • 运行pydtf.py,导入数据。
python3 pydtf.py

60

MongoDB 聚合函数MapReduce

MongoDB 有两种聚合函数:aggregate 与 mapreduce

mapreduce函数提供的是mapreduce(编程模型)的聚合操作,它的工作流程如下图所示:

78

MongoDB中的MapReduce主要有以下几阶段:

  • Map:把一个操作Map到集合中的每一个文档

  • Shuffle: 根据Key分组对文档,并且为每个不同的Key生成一系列(>=1个)的值表(List of values)。

  • Reduce: 处理值表中的元素,直到值表中只有一个元素。然后将值表返回到Shuffle过程,循环处理,直到每个Key只对应一个值表,并且此值表中只有一个元素,这就是MR的结果。

  • Finalize:此步骤不是必须的。在得到MR最终结果后,再进行一些数据“修剪”性质的处理。

查看数据格式

mongo # 启动mongo shell
use taobao
db.order_info.findOne() 

61

查询每个cust_id 的所有price总和

1.定义 map函数:

var mapFunction1 = function() {
                       emit(this.cust_id, this.price);
                   };

2.定义reduce函数:

var reduceFunction1 = function(keyCustId, valuesPrices) {
                          return Array.sum(valuesPrices);
                      };

3.执行mapreduce,输出结果到当前db的map_reduce_example集合中:

db.order_info.mapReduce(
                     mapFunction1,
                     reduceFunction1,
                     { out: "map_reduce_example" }
                   )

62

4.查询结果

db.map_reduce_example.find()

63

计算所有items 的平均库存

1.定义map函数

var mapFunction2 = function() {
                       for (var idx = 0; idx < this.items.length; idx++) {
                           var key = this.items[idx].sku;
                           var value = {
                                         count: 1,
                                         qty: this.items[idx].qty
                                       };
                           emit(key, value);
                       }
                    };

2.定义reduce函数

var reduceFunction2 = function(keySKU, countObjVals) {
                     reducedVal = { count: 0, qty: 0 };
                     for (var idx = 0; idx < countObjVals.length; idx++) {
                         reducedVal.count += countObjVals[idx].count;
                         reducedVal.qty += countObjVals[idx].qty;
                     }
                     return reducedVal;
                  };

3.定义finalize函数

var finalizeFunction2 = function (key, reducedVal) {
                       reducedVal.avg = reducedVal.qty/reducedVal.count;
                       return reducedVal;
                    };

64

4.执行mapreduce

db.order_info.mapReduce( mapFunction2,
                     reduceFunction2,
                     {
                       out: { merge: "map_reduce_example_2" },
                       finalize: finalizeFunction2
                     }
                   )

65

5.查看执行结果

db.map_reduce_example_2.find()

66

2.python操作MongoDB例2
编写python程序

1.创建python文件,命名为pyinsert.py

vim pyinsert.py

67

2.在pyinsert.py中编写如下代码:

from pymongo import MongoClient
from random import randint
'''定义用于生成随机名字信息列表'''
name1 = ["yang ", "li ", "zhou "]
name2 = [ "chao","hao","gao","qi gao","hao hao","gao gao","chao hao","ji gao","ji hao","li gao","li hao",]
provinces = ["guang dong", "guang xi", "shan dong","shan xi", "he nan"]
'''连接MongoDB'''
client = MongoClient('localhost', 27017)
db = client.student
sm = db.smessage
sm.remove()
'''循环生成学生信息'''
for i in range(1, 100):
    name = name1[randint(0, 2)] + name2[randint(0, 10)] 
    province = provinces[randint(0, 4)] 
    '''学生信息文档'''
    new_student = { 
        "name": name, 
        "age": randint(1, 30), 
        "province": province, 
        "subject": [ 
            {"name": "chinese", "score": randint(0, 100)}, 
            {"name": "math", "score": randint(0, 100)}, 
            {"name": "english", "score": randint(0, 100)}, 
            {"name": "chemic", "score": randint(0, 100)}, 
        ]}
    print(new_student) 
    '''插入MongoDB数据库'''
    sm.insert_one(new_student)
 
print(sm.count())

68

3.执行py代码

切换至pyinsert.py所在目录下,执行以下命令:

python3 pyinsert.py #运行代码

69

4.查看插入的数据

mongo # 启动mongo shell
use student
db.smessage.findOne()

70

在mongodb shell终端查询

1.查询广东学生的平均年龄。

db.smessage.aggregate({$match: {province: "guang dong"}},{$group: { _id: "$province", age:{$avg:"$age"}}})

71

2.查询所有省份的平均年龄。

db.smessage.aggregate({$group: { _id: "$province", age:{$avg:"$age"}}})

72

3.查询广东省所有科目的平均成绩。

db.smessage.aggregate({$match: {province: "guang dong"}},{$unwind: "$subject"},{$group: { _id: {province:"$province",sujname:"$subject.name"}, per:{$avg:"$subject.score"}}})

73

4.在题目3的基础上进行排序。

db.smessage.aggregate({$match: {province: "guang dong"}},{$unwind:"$subject"},{$group:{ _id:{province:"$province",sujname:"$subject.name"}, per:{$avg:"$subject.score"}}},{$sort:{per:1}})

74

3.python操作MongoDB例3
编写python程序

1.创建python文件,命名为pybbs.py

vim pybbs.py

94

2.在pybbs.py中编写如下代码:

from pymongo import MongoClient
from random import randint 

name = [
    'yangx',
    'yxxx',
    'laok',
    'kkk',
    'ji',
    'gaoxiao',
    'laoj',
    'meimei',
    'jj',
    'manwang',
]

title = [
    '123',
    '321',
    '12',
    '21',
    'aaa',
    'bbb',
    'ccc',
    'sss',
    'aaaa',
    'cccc',
]

client = MongoClient('localhost', 27017)
db = client.test
bbs = db.bbs
bbs.drop()

for i in range(1, 10000):
    na = name[randint(0, 9)]
    ti = title[randint(0, 9)]
    newcard = { 'author': na, 'title': ti,}
    bbs.insert_one(newcard)

print(bbs.estimated_document_count())

95

注:pybbs.py中的代码改了两个地方,如下图所示:

因原来的remove()方法和count()方法在5.0.2版本已经弃用,如下图所示

所以我把remove()换成了drop(),实现删除集合的功能,将count()换成了estimated_document_count(),实现一样的功能。

3.执行py代码

切换至pybbs.py所在目录下,执行以下命令:

python3 pybbs.py #运行代码

96

4.查看插入的数据

mongo # 启动mongo shell
use test
db.bbs.findOne()

97

98

99

在mongodb shell终端查询

1.查询每条记录的作者。

db.bbs.aggregate({“$project”:{“author”:1}})

100

2.用group将作者名称分组。

db.bbs.aggregate({"$group":{"_id":"$author","count":{"$sum":1}}})

101

3.在题目2的基础上进行排序。

db.bbs.aggregate({"$group":{"_id":"$author","count":{"$sum":1}}},{“$sort”:{“count”:-1}})

102

4.在题目3的基础上限制输出结果为5个

db.bbs.aggregate({"$group":{"_id":"$author","count":{"$sum":1}}},{“$sort”:{“count”:-1}},{“$limit”:5})

103

六.核心组件

核心组件——Mongod
  • mongod:此程序会处理所有的数据请求、管理数据格式并且执行用于后台管理的操作。
  • 当一个mongod在无任何参数的情况下运行时,它会连接到默认的数据目录/data/db,以及默认的端口27017,它会在这个端口上侦听socket的请求连接。

Mongodb启动命令mongod参数说明
–port arg # 指定服务端口号,默认端口27017
–bind_ip arg # 绑定服务IP,若绑定127.0.0.1,则只能本机访问,不指定默认本地所有IP
–logpath arg # 指定MongoDB日志文件,注意是指定文件不是目录
–logappend # 使用追加的方式写日志
–fork # 以守护进程的方式运行MongoDB,创建服务器进程
–auth # 启用验证
–cpu # 定期显示CPU的CPU利用率和iowait
–dbpath arg # 指定数据库路径

上述参数都可以写入 mongod.conf 配置文档里例如:

dbpath = /data/mongodb

logpath = /data/mongodb/mongodb.log

logappend = true

port = 27017

fork = true

auth = true
核心组件——Mongo

Mongo:为研发人员提供了一个交互式的JS API,这样方便在数据库上直接做测试查询和操作,并且也可用于系统管理员对数据库进行有效管理。

核心组件——Mongos

Mongos:被用于MongoDB的分片。相当于一种路由服务,它将对来自于应用层的查询请求进行处理并判断所请求的数据位于分片集群集的具体位置。

mongod.lock文件及oplog文件

在mongodb的启动时,在数据目录下,会生成一个mongod.lock文件。如果在正常退出时,会清除这个mongod.lock文件,若要是异常退出,在下次启动的时候,会禁止启动,从而保留一份干净的一份副本数据。有人可能会想到删除这个文件,建议请不要这么做。如果这么做,我们也不知道数据文件是否会损坏,如果mongod.lock文件阻止mongod的启动,请对数据文件进行修复,而不是简单的删除该文件。而这里的mongod.lock文件存放的是:启动mongod的进程号.

这里提到了"正常退出",详细介绍如下:
MongoDB 提供几种关闭服务的命令,具体为以下:

  • 使用 Crtl+C 关闭

光标:键入 Crtl+C 关闭
备注:如果以前台方式启动 MongoDB 服务,使用“Crtl+C” 服务会关闭,这种关闭方式会等待当前进行中的的操作完成,所以依然是干净的关闭方式。

  • 使用数据库命令关闭
[mongo@redhatB data]$ mongo
> use admin;
> db.shutdownServer();
  • 使用 mongod 命令关闭
[mongo@redhatB data]$ mongod  --shutdown  --dbpath /database/mongodb/data/

备注:mongod 命令的 shutdown 选项能干净的关闭 MongoDB 服务。

  • 使用 kill 命令

    • 查看 mongo 相关进程

      [mongo@redhatB data]$ ps -ef | grep mongo
      root     17573 14213  0 05:10 pts/1    00:00:00 su - mongo
      mongo    17574 17573  0 05:10 pts/1    00:00:00 -bash
      mongo    18288     1  0 06:12 ?        00:00:00 mongod -f /database/mongodb/data/mongodb_27017.conf
      mongo    18300 17574  6 06:13 pts/1    00:00:00 ps -ef
      mongo    18301 17574  0 06:13 pts/1    00:00:00 grep mongo
      
    • kill mongo 服务进程

      [mongo@redhatB data]$ kill 18288
      [mongo@redhatB data]$ ps -ef | grep pmon
      mongo    18304 17574  0 06:13 pts/1    00:00:00 grep pmon
      

      备注:可以使用操作系统的 kill 命令,给 mongod 进程发送 SIGINT 或 SIGTERM 信号,即 “kill -2 PID,” 或者 “kill -15 PID“。
      建议不要使用 ”kill -9 pid“,因为如果 MongoDB 运行在没开启日志(–journal)的情况下,可能会造成数据损失。

在mongo库中,oplog是数据存放和数据主从同步的,而在本地库local中,$ show collections 下,有个oplog.rs 的collection,解释下其中的字段:
{ ts : …, op: …, ns: …, o: … o2: … }
上面就是一条oplog信息,复制机制就是通过这些信息来进行节点间的数据同步并维护数据一致性的,其中

  • ts:8字节的时间戳,由4字节unix timestamp + 4字节自增计数表示。在选举(如master宕机时)新primary时,会选择ts最大的那个secondary作为新primary。
  • op:1字节的操作类型,例如i表示insert,d表示delete。
  • ns:操作所在的namespace。
  • o:操作所对应的document,即当前操作的内容(比如更新操作时要更新的的字段和值)
  • o2: 在执行更新操作时的where条件,仅限于update时才有该属性

其中op,可以是如下几种情形之一:
“i”: insert
“u”: update
“d”: delete
“c”: db cmd
“db”:声明当前数据库 (其中ns 被设置成为=>数据库名称+ ‘.’)
“n”: no op,即空操作,其会定期执行以确保时效性

七.复制

复制的目标

​ 保证数据在生产部署时的冗余和可靠性,通过在不同的机器上保存副本来保证数据的不会因为单点损坏而丢失。能够随时应对数据丢失、机器损坏带来的风险。
  换一句话来说,还能提高读取能力,用户的读取服务器和写入服务器在不同的地方,而且,由不同的服务器为不同的用户提供服务,提高整个系统的负载。

简单来说,我们需要实现以下目标:
1、Failover(故障转移,故障切换,故障恢复)
2、Redundancy(数据冗余)
3、避免单点,用于灾难时恢复,报表处理,提升数据可用性
4、读写分离,分担读压力
5、对用户透明的系统维护升级

复制的基础

MongoDB高可用分2种:复制集(Replica Sets)也称副本集
主从复制(Master-Slave)主从模式在MongoDB 3.6也彻底废弃不使用了。

MongoDB副本集架构如下所示:

83

在MongoDB中,创建一个副本集之后就可以使用复制功能了。

如果主服务器崩溃了,备份服务器会自动将其中一个成员升级为新的主服务器。

84

一个主库;两个从库组成,主库宕机时,这两个从库都可以被选为主库。

85

当主库宕机后,两个从库都会进行竞选,其中一个变为主库,当原主库恢复后,作为从库加入当前的复制集群即可。

86

MongoDB Oplog
MongoDB Oplog是MongoDB Primary和Secondary在复制建立期间和建立完成之后的复制介质,就是Primary中所有的写入操作都会记录到MongoDB Oplog中,然后从库会来主库一直拉取Oplog并应用到自己的数据库中。这里的Oplog是MongoDB local数据库的一个集合,它是Capped collection,通俗意思就是它是固定大小,循环使用的。如下图:

87

MongoDB Oplog中的内容及字段介绍:

{
"ts" : Timestamp(1446011584, 2), #操作时间,当前timestamp + 计数器,计数器每秒都被重置
"h" : NumberLong("1687359108795812092"),#操作的全局唯一标识
"v" : 2, #oplog版本信息
"op" : "i", #操作类型  i:插入操作u:更新操作d:删除操作c:执行命令(如createDatabase,dropDatabase)
"ns" : "test.nosql",#操作针对的集合
"o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" }
#操作内容,如果是更新操作
}

{—如何数据同步的??—}
MongoDB的Replica Sets架构是通过一个日志来存储操作的,这个日志就叫做 oplog 。
集群之间依靠oplog进行数据同步。
oplog特点:

  • 全名local.oplog.rs,位于local数据库下。
  • oplog是 Capped Collection 类型(定长集合,遵循FIFO原则)。
  • oplog中的每个文档都代表主节点上执行的一个操作。oplog只记录改变数据库状态的操作

{—Oplog的内容—}

88

实现复制集——创建副本集

1、使用–nodb选项启动一个mongo shell

     $ mongo --nodb

2、创建一个副本集replicaSet(3个节点)

     > replicaSet = new ReplSetTest({"nodes" : 3})

3、启动mongod服务器

replicaSet.startSet()     // 启动3个mongod进程
replicaSet.initiate()     // 配置复制功能

4、开启一个新的shell,在第二个shell中,连接到运行在31000端口的mongod:

conn1 = new Mongo("localhost:31000")
     connection to localhost:31000
     testReplSet:PRIMARY>

5、连接到conn1连接的test数据库

     testReplSet:PRIMARY> primaryDB = conn1.getDB("test")
     test

6、在连接到主节点的连接上执行isMaster命令,可以看到副本集的状态:

     testReplSet:PRIMARY> primaryDB.isMaster()
     isMaster返回的字段有点儿多,一个很重要的字段指明了是主节点("ismaster" : true)

1、在primary节点上做写入操作:插入1000个文档

 testReplSet:PRIMARY> for (i=0; i<1000; i++) { primaryDB.coll.insert({count: i}) }

2、检查集合的文档数量,确保真的插入成功了

 testReplSet:PRIMARY>primaryDB.coll.count()
 1000    //1000个文档,插入成功

3、检查其中一个副本集成员是否有刚刚写入的那些文档的副本。连接到任意一个备份节点:

testReplSet:PRIMARY> conn2 = new Mongo("localhost:31001")
connection to localhost:31001
testReplSet:SECONDARY>secondaryDB = conn2.getDB("test")
test

4、如果希望从备份节点读取数据,需要设置“从备份节点读取数据没有问题”标识

testReplSet:SECONDARY> conn2.setSlaveOk()

5、现在就可以从这个备份节点中读取数据了。使用普通的查询

testReplSet:SECONDARY> secondaryDB.coll.find()
{ "_id" : ObjectId("5037cac65f3257931833902b"), "count" : 0 }
...
testReplSet:SECONDARY> secondaryDB.coll.count()
1000

6、

testReplSet:SECONDARY> secondaryDB.coll.insert({"count" : 1001})  //失败
实现复制集——关闭副本集

自动故障转移:
1、先关掉主节点

 testReplSet:PRIMARY>primaryDB.adminCommand({"shutdown" : 1})

2、备份节点上执行isMaster,看看新的主节点是哪一个

 testReplSet:SECONDARY> secondaryDB.isMaster()

关闭副本集:
从第一个shell中将副本集关闭:

> replicaSet.stopSet()

89

八.分片机制

分片的概念

分片(sharding)是指将数据拆分,将其分散存放在不同的机器上的过程,有时也叫分区。
分片的目标之一是创建一个拥有3台、9台甚至3000台机器的集群,整个集群对应用程序来说就像是一台单机服务器。
MongoDB支持自动分片,可以摆脱手动分片的管理,集群自动切分数据,做负载均衡。

90

91

MongoDB的集群架构,一个典型的集群结构如下:

92

分片集群由分片、mongos路由器和配置服务器组成。

分片中各个角色的作用

  • 配置服务器:一个独立的mongod进程,保存集群和分片的元数据。
  • 路由服务器:即mongos,起到一个路由的功能,供程序连接。
  • 分片服务器:是一个独立普通的mongod进程,保存数据信息。可以是一个副本集也可以是单独的一台服务器。

分片技术解决的需求痛点

  • 高数据量和吞吐量的数据库应用会对单机的性能造成较大压力;
  • 大的查询量会将单机的CPU耗尽;
  • 大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到键盘IO上
分片的工作原理

{—分片的原理—}
MongoDB分片的基本思想:

  • 将集合切分成小块。这些块分散到若干片里面,每个片只负责总数据的一部分。
  • 应用程序不必知道哪片对应哪些数据,甚至不需要知道数据已经被拆分了,所以在分片之前要运行一个路由进程,该进程名为mongos。这个路由器知道所有数据的存放位置。
  • 对应用来说,它仅知道连接了一个普通的mongod。路由器知道数据和片的对应关系
  • 如果请求有了回应,路由器将其收集起来回送给应用。

{—何时进行分片?—}

适用场景

  • 单个节点的磁盘不足。
  • 单个mongodb已经不能满足写数据的性能要求。
  • 想把大量数据放到内存里提高性能。
管理分片

{—MongoDB分片过程—}

  • 启动配置服务器
  • 启动mongos
  • 添加mongod实例(片)
  • 对数据库启用分片
  • 对集合进行分片

{—MongoDB分片过程—}

  • 开启config服务器 。
       mongod --dbpath D:\sharding\config_node --port 2222
  • 开启mongos服务器 。

    mongos --port 3333 –configdb 127.0.0.1:2222
    
  • 启动mongod服务器 。

    mongod --dbpath E:\sharding\mongod_node1 --port 4444
    mongod --dbpath E:\sharding\mongod_node2 --port 5555 
    
  • 服务配置 。

    mongo localhost:3333/admin
    db.runCommand({“addshard”:”127.0.0.1:4444”,allowlocal:true})
    db.runCommand({“addshard”:”127.0.0.1:5555”,allowlocal:true})
    
  • 开启数据库分片功能

    db.runCommand({“enablesharding”:”test”})
    
  • 指定集合中分片的片键

    db.runCommand({“shardcollection”:”test.person”,”key”:{“name”:1}})
    
  • 通过mongos插入10w记录,然后通过printShardingStatus命令查看mongodb的数据分片情况。

93

复制与分片的区别:
复制是让多台服务器都拥有同样的数据副本,每一台服务器都是其他服务器的镜像,而每一个分片都和其他分片拥有不同的数据子集。

九.MongoDB副本集与分片部署实例

内容

MongoDB集群的部署方式主要有:主从复制、副本集、分片模式,而从3.6版本开始,主从模式已经废弃不用了,分片模式也只能添加副本集成员。

本次实验的内容为在自己电脑上部署MongoDB的副本集与分片模式,可以在单一的虚拟机、单一的Windows、或者多个虚拟机和Window混合的方式进行部署。以下步骤针对一台windows与windows上的一台Linux虚拟机进行部署。

假设部署2个副本集ws, us,每个副本集包含3个成员,2个副本集分别为一个分片服务器,对2个副本集创建数据目录和日志名称分别为w1,w2,w3和u1,u2,u3,具体结构如下图:

即在windows系统中放置副本集ws的2个成员w1、w2和us的一个成员u1,配置信息的副本集config也放置2个成员在windows中,2台机器各开启一个路由入口

副本集部署

1.确定windows和linux系统的IP,确保两边IP能连通

在windows中打开cmd界面输入ipconfig,在Linux中输入ifconfig分别查看IP,可以通过ping命令查看两边IP是否能连通

2.复制已经配置文件或创建配置文件,也可以在启动的时候直接指定参数

linux中配置文件为/etc/mongod.conf,window中安装后在bin目录查看是否有mongod.cfg文件,复制后修改里面的dbPath,logPath,port,bindIp这几个参数,注意logpath要配置到文件名称,每一个副本集成员的数据目录不能相同,例如以下在window中配置w1,其他文件类似配置

3.创建对应的数据目录和日志目录

按照配置文件中的路径创建数据和日志目录,只需要创建到目录,不需要创建文件,在window中直接新建文件夹即可,linux中通过mkdir命令创建目录

4.分别启动副本集ws和us

启动命令:

mongod -f 配置文件路径 --replSet ws(或者us) --shardsvr

或者mongod --config 配置文件路径 --replSet ws(或者us) --shardsvr

例如:mongod -f D:\mongodb\config\w1.cfg --replSet ws --shardsvr

注意:

1.在window中,需要把安装目录下的bin目录加入环境变量后才可以直接调用mongod命令,否则需要定位到bin目录或输入完整路径调用

2.–replSet后面的是副本集的名称,同一个副本集成员启动时名称需要保持一致

3.–shardsvr表示启动为分片,这个参数也可以在部署分片服务器时再加上

5.对两个副本集分别连接其中的某一个服务器进行初始化

例如对副本集进入27011端口进行初始化

mongosh 192.168.67.1:27011

或者mongosh --host 192.168.67.1 --port 27011

也可以通过mongo命令进入,其中第二种方式host指定了要连接的数据库IP,port指定连接的端口

进入shell界面后通过rs.initiate()方法可以进行初始化,执行该方法后当前服务器会初始化成primary成员,然后可以通过rs.add(‘IP:PORT’)添加其他成员进副本集,这里我们把widows中的27012端口和linux中的27013端口的服务添加到ws副本集

rs.add(‘192.168.67.1:27012’)

rs.add(‘192.168.67.131:27013’)

添加完成员后另外2个服务器也会自动初始化为secondary成员,此时可以用rs.status()方法查看当前副本集的状态和成员信息

6.测试副本集的复制功能

确定我们添加数据的服务器是primary,这个可以在shell界面输入行那里看到是primary还是secondary,随便添加数据,然后进入其他成员的shell界面查看数据是否添加成功

注意:默认情况下,副本集的读写请求都是通过primary成员来进行的,如果要从secondary中读取数据,需要设置一下

1.在secondary成员中调用find()方法后再加一个方法readPref()设置从secondary获取数据,这种方法每次查询都需要调用一下,如:db.col.find().readPref(‘secondary’’)

2.如果不想每次都调用readPref()方法,可以在secondary中调用以下方法设置从secondary中读取数据:db.getMongo().setReadPref(‘secondary’),设置之后直接调用find()方法就能查询数据了

readPref()和setReadPref()方法的参数可以是以下的一种:

1.primary:主节点,默认模式,读操作只在主节点,如果主节点不可用,报错或者抛出异常。

2.primaryPreferred:首选主节点,大多情况下读操作在主节点,如果主节点不可用,如故障转移,读操作在从节点。

3.secondary:从节点,读操作只在从节点, 如果从节点不可用,报错或者抛出异常。

4.secondaryPreferred:首选从节点,大多情况下读操作在从节点,特殊情况(如单主节点架构)读操作在主节点。

5.nearest:最邻近节点,读操作在最邻近的成员,可能是主节点或者从节点。

分片部署

1.确保上一步部署副本集时,在启动时添加了–shardsvr参数以分片服务器启动

从3.6版本开始,分片服务器只能是一个副本集,如果你需要部署单一服务为分片服务器,可以把它设置成单成员的副本集再启动为分片服务器

2.部署config服务器

与副本集部署类似,在启动时把–shardsvr改成–configsvr表示启动为配置服务器,这里我们把端口分别配置成27031,27032,27033,集群名称为config

3.启动mongos服务

mongos是路由服务器,它可以读取配置服务器保存的数据和分片之间的元数据,让整个集群看起来像一个数据库。

mongos --configdb config/192.168.67.1:27031,192.168.67.1:27032,192.168.67.131:27033 --logpath 日志路径 --logappend --port 27017

注意:

1.这里启动是mongos并非mongod,mongos不需要配置dbpath,因为不存储数据,

2.logpath等参数同样也可以放到配置信息里

3.–port指定mongos端口,不写默认以27017端口启动,configdb参数指定了mongos使用的配置服务器列表

4.不绑定IP时可以通过127.0.0.1来连接

5.mongos不需要配置成副本集模式,也可以把日志文件和端口放到配置文件里启动:

mongos --configdb config/192.168.67.1:27031,192.168.67.1:27032,192.168.67.131:27033 -f 配置文件路径

4.添加分片信息

通过mongo或mongosh连接mongos端口即可登录路由服务器的shell界面

mongosh --host 192.168.67.1 --port 27017

添加分片:sh.addShard(‘us/IP地址列表’)

或者:db.runCommand({addShard: ‘us/IP地址列表’, name: ‘us_shard’, maxSize: 10240})

注意:

1.通过runCommand()命令可以指定分片的名称和数据存储大小

2.ip列表可以不需要列出所有IP,只填写一个IP即可,系统会自动检查副本集成员

5.设置数据分片

默认情况下,MongoDB不会自动对数据进行分片,要对数据进行分片,首先需要设置数据库允许分片,接着对数据库的集合进行分片设置,未对集合进行分片设置时集合会保存在主分片中

对数据库设置允许分片:sh.enableSharding(“test”)

对test数据库里的集合col分片:sh.shardCollection(‘test.col’, {_id: 1})

第一个参数为数据库和集合的名称,第二个参数表示进行分片的片键,类似主键,即数据是按照片键范围进行拆分的。

5.测试分片

默认数据块大小为64MB,连接上mongos后可以通过以下命令在config数据库中配置数据块的大小

use config

db.settings.insertOne({_id: ‘chunksize’, value: 1})

注意:

1.如果settings集合存在可使用update方法更新

2._id设置成chunsize表示配置数据块的大小,value单位是MB,范围可以设置1-1024

往集合中添加多条数据,通过sh.status()可以看到分块信息,也可以通过mongo或mongosh连接到对应的副本集验证是否只能查询到分片的数据子集

报错总结

报错1:“Error parsing YAML config file: yaml-cpp: error at line 2, column 13: illegal map value”

原因:mongodb 3.0之后配置文件采用YAML格式,这种格式非常简单,使用:表示,开头使用“空格”作为缩进。需要注意的是,“:”之后有value的话,需要紧跟一个空格,如果key只是表示层级,则无需在“:”后增加空格(比如: systemLog:后面既不需要空格)。按照层级,每行4个空格缩进,第二级则8个空格,依次轮推,顶层则不需要空格缩进。如果格式不正确,将会出现上面的错误

解决办法:

在修改配置文件mongod.cfg等时,修改缩进,按照层级,每行4个空格缩进,第二级则8个空格,依次轮推。

报错2:mongosh没有安装导致的mongosh命令不可用

解决办法:

安装 mongosh

MongoDB的Shell工具mongosh是一个全功能的JavaScript和Node.js的14.x REPL与MongoDB的部署交互环境。我们通过它可以直接对数据库进行查询和操作。这个工具是需要在安装玩MongoDB后单独安装的,可以自己在MongoDB官网下载页面寻找对应的版本(ubuntu/windows/…)进行下载安装:

  • https://www.mongodb.com/try/download/shell?jmp=docs

技巧1:Windows下查看某个端口被谁占用

1、打开命令窗口(以管理员身份运行)

开始–>运行–>cmd

2、查找所有运行的端口

输入命令:

netstat -ano

技巧2:Ubuntu查看端口使用情况,使用netstat命令

查看已经连接的服务端口(ESTABLISHED)

netstat -a

查看所有的服务端口(LISTEN,ESTABLISHED)

netstat -ap

查看指定端口,可以结合grep命令:

netstat -ap | grep 8080

也可以使用lsof命令:

lsof -i:8888

若要关闭使用这个端口的程序,使用kill + 对应的pid

kill -9 PID号

ps:kill就是给某个进程id发送了一个信号。默认发送的信号是SIGTERM,而kill -9发送的信号是SIGKILL,即exit。exit信号不会被系统阻塞,所以kill -9能顺利杀掉进程。

本集,如果你需要部署单一服务为分片服务器,可以把它设置成单成员的副本集再启动为分片服务器

2.部署config服务器

与副本集部署类似,在启动时把–shardsvr改成–configsvr表示启动为配置服务器,这里我们把端口分别配置成27031,27032,27033,集群名称为config

3.启动mongos服务

mongos是路由服务器,它可以读取配置服务器保存的数据和分片之间的元数据,让整个集群看起来像一个数据库。

mongos --configdb config/192.168.67.1:27031,192.168.67.1:27032,192.168.67.131:27033 --logpath 日志路径 --logappend --port 27017

注意:

1.这里启动是mongos并非mongod,mongos不需要配置dbpath,因为不存储数据,

2.logpath等参数同样也可以放到配置信息里

3.–port指定mongos端口,不写默认以27017端口启动,configdb参数指定了mongos使用的配置服务器列表

4.不绑定IP时可以通过127.0.0.1来连接

5.mongos不需要配置成副本集模式,也可以把日志文件和端口放到配置文件里启动:

mongos --configdb config/192.168.67.1:27031,192.168.67.1:27032,192.168.67.131:27033 -f 配置文件路径

4.添加分片信息

通过mongo或mongosh连接mongos端口即可登录路由服务器的shell界面

mongosh --host 192.168.67.1 --port 27017

添加分片:sh.addShard(‘us/IP地址列表’)

或者:db.runCommand({addShard: ‘us/IP地址列表’, name: ‘us_shard’, maxSize: 10240})

注意:

1.通过runCommand()命令可以指定分片的名称和数据存储大小

2.ip列表可以不需要列出所有IP,只填写一个IP即可,系统会自动检查副本集成员

5.设置数据分片

默认情况下,MongoDB不会自动对数据进行分片,要对数据进行分片,首先需要设置数据库允许分片,接着对数据库的集合进行分片设置,未对集合进行分片设置时集合会保存在主分片中

对数据库设置允许分片:sh.enableSharding(“test”)

对test数据库里的集合col分片:sh.shardCollection(‘test.col’, {_id: 1})

第一个参数为数据库和集合的名称,第二个参数表示进行分片的片键,类似主键,即数据是按照片键范围进行拆分的。

5.测试分片

默认数据块大小为64MB,连接上mongos后可以通过以下命令在config数据库中配置数据块的大小

use config

db.settings.insertOne({_id: ‘chunksize’, value: 1})

注意:

1.如果settings集合存在可使用update方法更新

2._id设置成chunsize表示配置数据块的大小,value单位是MB,范围可以设置1-1024

往集合中添加多条数据,通过sh.status()可以看到分块信息,也可以通过mongo或mongosh连接到对应的副本集验证是否只能查询到分片的数据子集

报错总结

报错1:“Error parsing YAML config file: yaml-cpp: error at line 2, column 13: illegal map value”

原因:mongodb 3.0之后配置文件采用YAML格式,这种格式非常简单,使用:表示,开头使用“空格”作为缩进。需要注意的是,“:”之后有value的话,需要紧跟一个空格,如果key只是表示层级,则无需在“:”后增加空格(比如: systemLog:后面既不需要空格)。按照层级,每行4个空格缩进,第二级则8个空格,依次轮推,顶层则不需要空格缩进。如果格式不正确,将会出现上面的错误

解决办法:

在修改配置文件mongod.cfg等时,修改缩进,按照层级,每行4个空格缩进,第二级则8个空格,依次轮推。

报错2:mongosh没有安装导致的mongosh命令不可用

解决办法:

安装 mongosh

MongoDB的Shell工具mongosh是一个全功能的JavaScript和Node.js的14.x REPL与MongoDB的部署交互环境。我们通过它可以直接对数据库进行查询和操作。这个工具是需要在安装玩MongoDB后单独安装的,可以自己在MongoDB官网下载页面寻找对应的版本(ubuntu/windows/…)进行下载安装:

  • https://www.mongodb.com/try/download/shell?jmp=docs

技巧1:Windows下查看某个端口被谁占用

1、打开命令窗口(以管理员身份运行)

开始–>运行–>cmd

2、查找所有运行的端口

输入命令:

netstat -ano

技巧2:Ubuntu查看端口使用情况,使用netstat命令

查看已经连接的服务端口(ESTABLISHED)

netstat -a

查看所有的服务端口(LISTEN,ESTABLISHED)

netstat -ap

查看指定端口,可以结合grep命令:

netstat -ap | grep 8080

也可以使用lsof命令:

lsof -i:8888

若要关闭使用这个端口的程序,使用kill + 对应的pid

kill -9 PID号

ps:kill就是给某个进程id发送了一个信号。默认发送的信号是SIGTERM,而kill -9发送的信号是SIGKILL,即exit。exit信号不会被系统阻塞,所以kill -9能顺利杀掉进程。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值