MongoDB基础

文章目录

MongoDB

官方文档:https://docs.mongodb.com/

中文文档:https://www.mongodb.org.cn/

mongoDB的生态、理念非常先进而且成熟、但是mongoDB不仅有开源版本,还有企业版本。所以有部分公司比较担心,哪天无法使用mongoDB了,所以也会产生一些替代产品。

DynamoDB  : AWS
SequoiaDB : 巨杉数据库

基本介绍

MongoDB 是由C++语言编写并基于分布式文件存储的开源数据库,属于NOSQL 。

MongoDB 是一款介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的NOSQL数据库。它面向文档存储,而且安装和操作起来都比较简单和容易,而且它支持各种流行编程语言进行操作,如Python,Node.js,Java,C++,PHP,C#等。

目前在大数据、内容管理、持续交付、移动应用、社交应用、用户数据管理、数据中心等领域皆有广泛被使用。

什么是mongodb?

MongoDB是一个文档数据库,提供好的性能,领先的非关系型数据库。采用BSON存储文档数据。

BSON()是一种类json的一种二进制形式的存储格式,全称:Binary JSON.

相对于json多了date类型和二进制数组。

为什么用MongoDB?

  1. 数据结构简单,没有固定的数据结构

  2. 没有复杂的连接,没有复杂的外键约束

  3. 深度查询能力,MongoDB支持动态查询,查询api是基于v8引擎(javascript)。

  4. 容易调试

  5. 容易扩展

  6. 不需要转化/映射应用对象到数据库对象

  7. 使用内部内存作为存储工作区,以便更快的存取数据。

mongodb的应用场景

  1. 大数据
  2. 内容管理系统
  3. 移动端App
  4. 数据管理
  5. 日志处理
  6. 游戏道具处理

MongoDB相对于RDBMS的优势

游戏道具:

​ [{"id":1, "name": "衣服", "防御值": 1000},

​ {"id":2, "name": "武器", "攻击力": 9999, "攻击距离": 50},

​ {"id":3, "name": "鞋子", "移动速度": 1.1, "颜色": "蓝色"},]

id name 防御值 攻击力 攻击距离 移动速度 颜色
2 武器 None 9999 50 None None
1 衣服 1000 None None None None
3 鞋子 None None None 1.1 蓝色
  • 无固定结构 。面向文档,以 JSON 格式的文档保存数据,数据结构由键值(key=>value)对组成。MongoDB 的文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组,单个对象的结构是清晰的。
  • 没有复杂的表连接。不需要维护表与表之间的内在关联关系。
  • 查询功能强大,丰富的查询语言。MongoDB的查询功能几乎与SQL一样强大,使用基于文档的查询语言,可以对文档进行动态查询,提供丰富的查询操作和索引支持,也有事务操作,可以更快地更稳定的访问数据。(mongoDB4.0以后才真正支持所谓的多文档事务操作)
  • 易于调优和扩展。具备高性能、高可用性及可伸缩性等特性
  • 应用程序对象与数据库对象天然对应。
  • 可分片,对数据存储友好,可以基于内存存储或者硬盘文件存储。
  • 任何属性都可以建立索引,而且提供丰富多样的索引。

术语对比

RDBMS Mongodb 描述
库(database) 库(database)
表(Table) 集合(Collection)
行/记录(Row/Record) 文档(Document) Document就是json结构的一条数据记录
列/字段(Column/Field) 字段/键/域(Field) 属性字段名
主键(Primary Key) 对象ID(ObjectId) _id: ObjectId("10c191e8608f19729507deea")
索引(Index) 索引(Index) 都有普通索引, 唯一索引, 主键索引, 联合索引这么区分的

MySQL 与 MongoDB 的差别

差别在多方面,例如:数据的表示、查询、关系、事务、模式的设计和定义、速度和性能。

MongoDB 是由 C++语言编写的,是一个基于分布式文件存储的开源数据库系统。

在高负载的情况下,添加更多的节点,可以保证服务器性能。

MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。

MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

MongoDB 是一个面向文档的数据库,目前由 10gen 开发并维护,它的功能丰富齐全,所以完全可以替代 MySQL。

与 MySQL 等关系型数据库相比,MongoDB 的优点如下:

  1. 弱一致性,更能保证用户的访问速度。

  2. 文档结构的存储方式,能够更便捷的获取数据。

  3. 内置 GridFS,支持大容量的存储。

  4. 内置 Sharding。

  5. 第三方支持丰富。(这是与其他的 NoSQL 相比,MongoDB 也具有的优势)

  6. 性能优越

MongoDB 相对于MySQL,它还算比较年轻的一个产品,所以它的问题,就是成熟度肯定没有传统 MySQL那么成熟稳定。所以在使用的时候,第一,尽量使用稳定版,不要在线上使用开发版,这是一个大原则;

另外一点,备份很重要,MongoDB 如果出现一些异常情况,备份一定是要能跟上。除了通过传统的复制的方式来做备份,离线备份也还是要有,不管你是用什么方式,都要有一个完整的离线备份。往往最后出现了特殊情况,它能帮助到你;

另外,MongoDB 性能的一个关键点就是索引,索引是不是能有比较好的使用效率,索引是不是能够放在内存中,这样能够提升随机读写的性能。如果你的索引不能完全放在内存中,一旦出现随机读写比较高的时候,它就会频繁地进行磁盘交换,这个时候,MongoDB 的性能就会急剧下降,会出现波动。

另外,MongoDB 还有一个最大的缺点,就是它占用的空间很大,因为它属于典型空间换时间原则的类型。那么它的磁盘空间比普通数据库会浪费一些,而且到目前为止它还没有实现在线压缩功能,在 MongoDB 中频繁的进行数据增删改时,如果记录变了,例如数据大小发生了变化,这时候容易产生一些数据碎片,出现碎片引发的结果,一个是索引会出现性能问题,另外一个就是在一定的时间后,所占空间会莫明其妙地增大,所以要定期把数据库做修复,定期重新做索引,这样会提升 MongoDB 的稳定性和效率。

成功运行了mongoDB以后,对于mongod和mongosh的区别

mongod是处理MongoDB系统的主要进程。主要负责处理数据请求,管理数据存储和执行后台管理操作。当我们运行mongod命令意味着正在启动MongoDB进程, 并且在后台运行。

mongosh是一个命令行实现的客户端操作mongodb的工具,用于连接一个特定的mongod实例。当我们没有带参数运行mongosh命令它将使用默认的localhost:27017和mongod进行连接。

mongoDB的默认端口:27017

bash 复制代码
# 查看交互终端的版本
mongosh --version
# 或者终端内部使用 version()

sc stop MongoDB

sc delete MongoDB

mongod --config "D:\mongo\bin\mongod.cfg" --install

net start MongoDB

sc query MongoDB

基本操作

通用操作

查看帮助文档
javascript 复制代码
help

终端效果:

bash 复制代码
db                    查看当前用户所在数据库的数据库对象
use <db>         set current database   切换操作的数据库
show                 'show databases'/'show dbs': Print a list of all available databases. 查看当前系统所有的数据库
                         'show collections'/'show tables': Print a list of all collections for current database. 查看当前数据库所有的数据集合
                         'show profile': Prints system.profile information.  查看当前系统的环境配置
                         'show users': Print a list of all users for current database. 查看当前数据库中所有的管理员用户
                         'show roles': Print a list of all roles for current database.  查看当前数据库中所有的管理员角色
                         'show log <type>': log for current connection, if type is not set uses 'global'  查看指定日志信息
                         'show logs': Print all logs.  查看全部日志
it                       result of the last line evaluated; use to further iterate  查看更多的查询结果,相当于下一页
exit                    quit the mongo shell  退出终端
sleep                 Sleep for the specified number of milliseconds   睡眠函数,指定睡眠毫秒
load                  Loads and runs a JavaScript file into the current shell environment  加载指定js文件的js代码到当前mongo shell交互终端下
cls                     Clears the screen like console.clear()   清屏
print                  Prints the contents of an object to the output   打印输出一个终端对象
当前服务器状态
javascript 复制代码
db.serverStatus()

终端效果:

bash 复制代码
{
	"host" : "ubuntu",    # 主机名
	"version" : "6.0.2",  # mongodb server 版本
	"process" : "mongod", # mongodb进程,主要有mongod和mongos(分片集群中)两种
	"pid" : NumberLong(1034),  # mongod的pid进程号,可以在linux终端下使用命令 pidof mongod 验证
	"uptime" : 105063,    # mongodb服务启动的秒数,时间短则表示近期有重启,时间长则表示在稳定运行
	"uptimeMillis" : NumberLong(105063193), # mongod服务启动的毫秒数
	"uptimeEstimate" : NumberLong(105063),  # mongod内部自己计算的启动秒数
	"localTime" : ISODate("2020-12-08T16:01:08.230Z"), # 本地时间,相当于 python的 datetime.now()
	# 客户端连接数相关 
	"connections" : {
		"current" : 8,  # 当前连接数的编号
		"available" : 51192, # 可用最大连接数
		"totalCreated" : 1,  # 截止目前为止总共历史创建的连接数
		"active" : 1,   # 还在活跃的连接数,查看并发连接数主要参考这个值
	},
        # 全局锁相关信息
	"globalLock" : {
		"totalTime" : NumberLong("105063115000"), # mongod启动后到现在的总时间,单位微秒
		"currentQueue" : { # 当前等待锁队列
			"total" : 0,   # 当前全局锁的等待个数
			"readers" : 0, # 当前全局读锁等待个数
			"writers" : 0  # 当前全局写锁等待个数
		},
		"activeClients" : {
			"total" : 0,   # 当前活跃客户端的个数
			"readers" : 0, # 当前活跃客户端中进行读操作的个数
			"writers" : 0  # 当前活跃客户端中进行写操作的个数
		}
	},

	"network" : { # 网络相关
		"bytesIn" : NumberLong(1611),    # 数据库接收到的网络传输字节数
		"bytesOut" : NumberLong(51269),  # 从数据库发送出去的网络传输字节数
		"numRequests" : NumberLong(16),  # mongod接收到的总请求次数
	},
	
	# 操作计数器
	"opcounters" : {
		"insert" : NumberLong(0),  # 本次mongod实例启动至今收到的插入操作总数 
		"query" : NumberLong(287), # 本次mongod实例启动至今收到的查询总数。
		"update" : NumberLong(0),  # 本次mongod实例启动至今收到的更新操作总数 。
		"delete" : NumberLong(0),  # 本次mongod实例启动至今的删除操作总数。
		"getmore" : NumberLong(0), # 本次mongod实例启动至今"getmore"操作的总数。
		"command" : NumberLong(588)# 本次mongod实例启动至今向数据库发出的命令总数 。
	},

	# 存储引擎,是MongoDB的核心组件, 负责管理数据如何存储在硬盘(Disk)和内存(Memory)上的组件
	# MongoDB 支持多种不同的存储引擎(Storage Engine),MongoDB支持的存储引擎有:WiredTiger,MMAPv1和In-Memory。
	# 1. WiredTiger,将数据持久化存储在硬盘文件中;从MongoDB 3.2 版本开始,成为MongDB默认存储引擎
	# 2. In-Memory,将数据存储在内存中
	# 3. MMAPv1,将数据持久化存储在硬盘文件中; MongoDB 3.2 版本以前的默认存储引擎,类似mysql里面的 MyISAM
	
	# WiredTiger 是比MMAPv1更好用,更强大的存储引擎,WiredTiger的写操作会先写入缓存(Cache)中,并持久化到WAL(Write ahead log,写日志),每60s或日志文件达到2GB时会做一次Checkpoint(检查点),将当前数据进行持久化,产生一个新的快照。Wiredtiger连接初始化时,首先将数据恢复至最新的快照状态,然后根据WAL恢复数据,以保证存储可靠性。
	# Checkpoint,检测点。将内存中的数据变更冲刷到磁盘中的数据文件中,并做一个标记点。
	#             表示此前的数据表示已经持久存储在了数据文件中,此后的数据变更存在于内存(CPU缓存)和WAL日志中.
	#             是一种让数据库redo(重做)和data(数据)文件保持一致的机制。这种机制 ,并非Mongodb独有的,mysql中的InnoDB也有。

	"storageEngine" : {
		"name" : "wiredTiger",   # 当前mongoDB系统默认的存储引擎是 wiredTiger
		"supportsCommittedReads" : true,
		"oldestRequiredTimestampForCrashRecovery" : Timestamp(0, 0),
		"supportsPendingDrops" : true,
		"dropPendingIdents" : NumberLong(0),
		"supportsTwoPhaseIndexBuild" : true,
		"supportsSnapshotReadConcern" : true,
		"readOnly" : false,
		"persistent" : true,
		"backupCursorOpen" : false
	},
	
        # 多文档事务,mongodb4.0以后新增特性
	"transactions" : {
		"retriedCommandsCount" : NumberLong(0),
		"retriedStatementsCount" : NumberLong(0),
		"transactionsCollectionWriteCount" : NumberLong(0),
		"currentActive" : NumberLong(0),
		"currentInactive" : NumberLong(0),
		"currentOpen" : NumberLong(0),
		"totalAborted" : NumberLong(0),
		"totalCommitted" : NumberLong(0),
		"totalStarted" : NumberLong(0),
		"totalPrepared" : NumberLong(0),
		"totalPreparedThenCommitted" : NumberLong(0),
		"totalPreparedThenAborted" : NumberLong(0),
		"currentPrepared" : NumberLong(0)
	},
	"locks":{ # 锁相关
	
	},
	"mem" : { # 内存相关
		"bits" : 64, # 操作系统位数
		"resident" : 18,  # 物理内存消耗,单位:M
		"virtual" : 1566, # 虚拟内存消耗,单位:M
		"supported" : true # 是否显示额外的内存信息
	},
}
查看当前db的连接机器地址
db.getMongo()
查看日志
javascript 复制代码
show logs
// global
// startupWarnings

// 如果要查看具体文件的日志。
show log global   
// global是全局日志,默认保存的日志文件在 /var/log/mongodb/mongod.log
// 如果mongoDB无法启动,查看错误就可以来到默认日志文件查看
数据备份与恢复

MongdoDB一共提供了4个命令行工具给我们对数据进行备份与恢复以及导入与导出数据。

备份与恢复操作的数据文件格式是bson格式,二进制文件,不需要进入mongo终端

导入与导出数据的数据文件格式是json格式,文本文档格式,不需要进入mongo终端

准备测试数据

mongoDB终端下进行以下操作:

javascript 复制代码
// 进入mongo交互终端
mongosh

use test  // mongoDB中可以使用use切换数据库,针对不存在的数据库会自动创建,可通过db在终端查看当前所在数据库

// 声明了一个函数,生成一个指定随机整数,不足10,前面补0
formatnumber = (start, end)=>{
    num = Math.round(Math.random() * (end-start)) + start
    if(num<10){
        return "0"+num;
    }else{
        return num;
    }
}

// 声明了一个函数,生成具有随机标题
rand_title=()=>{num = Math.round( Math.random() * 10 );num1 = Math.round( Math.random() * 10 );return [
        "赠送礼品-"+num,
        "购物狂欢-"+num,
        "随便买买-"+num,
        "愉快购物-"+num,
        "赠送礼物-"+num,
        "商品购买-"+num,
        "买多送多-"+num,
        "买年货-"+num,
        "买买买买-"+num,
        "充值会员-"+num][num1];}

// 创建一个函数,循环生成指定的数据
function rand_data(size=200000)
{for(var i=0; i<size; i++)
{db.orders.insertOne({"order_number": ( "0000000000000000" + i ).substr( String(i).length ),"date": "20"+formatnumber(0,21)+"-"+formatnumber(1,12)+"-"+formatnumber(1,31),"title": rand_title(i),"user_id": parseInt(i/200)+1,"items" :[{"goods_id" : parseInt(i/200)+1,"goods_number" : formatnumber(2, 10),"price" : formatnumber(50, 1000)},{ "goods_id" : parseInt(i/200)+2,"goods_number" :formatnumber(2, 10),"price" : formatnumber(50, 1000)}]});if(i%10000==0){print("已经添加了"+Math.ceil(i/10000)+"万条数据!");}}}


// 调用上面生成测试数据的函数
rand_data()

// 查看上面生成的数据
db.orders.find()
// 每次显示的数据,mongoDB交互终端默认只会显示20条,所以如果要查看更多,则根据提示输入it可以查看下一页数据。
数据备份

命令格式:

bash 复制代码
mongodump -h dbhost -d dbname -o dbdirectory

参数说明:

选项 作用 备注
-h MongoDB服务端地址和端口的简写 --host=MongoDB地址 --port=端口
-d 备份的数据库名称 --db=数据库名称
-o 备份数据保存目录所在路径 备份数据保存目录需要提前创建

例如:备份上面的test数据库

bash 复制代码
mongodump -h 127.0.0.1:27017 -d test -o /home/moluo/Desktop/

# 删除demo数据库
mongosh
use test
db.dropDatabase()
# 再次查看,可以发现没有任何数据了
db.orders.find()
数据恢复

命令格式:

bash 复制代码
mongorestore -h dbhost -d dbname --dir dbdirectory --drop

参数说明:

选项 作用 备注
-h MongoDB服务端地址和端口的简写 --host=MongoDB地址 --port=端口
-d 恢复的数据库名称 --db=数据库名称
--dir 备份数据所在目录
--drop 恢复数据前,先删除MongoDB中的旧数据

例如:恢复上面备份的test数据库

bash 复制代码
mongorestore -h 127.0.0.1:27017 -d test --dir /home/moluo/Desktop/test  --drop

# 进入数据库查看
use test
# 统计当前数据库下orders集合里面所有的文档总数
db.orders.countDocuments()
数据导出

命令格式:

bash 复制代码
mongoexport -h dbhost -d dbname -c collectionname -o file --type json/csv -f field

参数说明:

选项 作用 备注
-h MongoDB服务端地址和端口的简写 --host=MongoDB地址 --port=端口
-d 要导出的数据库名称 --db=数据库名称
-c 要导出的集合名称 --collection=集合名称
-o 导出数据保存的文件名
-f 导出数据的文档字段列表 如果不执行-f,则默认导出文档的全部字段
--type 导出数据的文件格式 默认是json,也可以是csv,当数据格式为csv时,另需加上-f "字段1,字段2,..."

例如:导出上面的test数据库的orders集合的所有数据

bash 复制代码
mongoexport  -h 127.0.0.1:27017 -d test -c orders -o /home/moluo/Desktop/test_orders.json --type json
数据导入

命令格式:

bash 复制代码
mongoimport -h dbhost -d dbname -c collectionname --file filename --type json/csv  --headerline -f field

参数说明:

选项 作用 备注
-h MongoDB服务端地址和端口的简写 --host=MongoDB地址 --port=端口
-d 要导入的数据库名称 --db=数据库名称
-c 要导入的集合名称 --collection=集合名称
--file 导入数据保存的文件名
--type 导入数据的文件格式 默认是json, 也可以是csv,当数据格式为csv时: 1. 需加上-f "字段1,字段2,..." 2. 可以选择加上--headerline,把首行视为导入字段行,不认为是实际数据

例如:把上面导出的json文件中的数据,导入到demo数据库的orders集合中

bash 复制代码
mongoimport   -h 127.0.0.1:27017 -d test -c orders --file /home/moluo/Desktop/test_orders.json --type json

用户管理

创建用户
javascript 复制代码
db.createUser(user, pwd, writeConcern)

创建一个数据库新用户用db.createUser()方法,如果用户存在则返回一个用户重复错误。

错误信息:uncaught exception: Error: couldn't add user: User "用户名@数据库" already exists

详细语法

javascript 复制代码
db.createUser({
    user: "<用户名>",
    pwd: "<密码>",
    customData: { <any information> }, // 任意内容,主要是为了表示用户身份的相关介绍 
    roles: [ // 角色和权限分配,一个用户可以绑定多个角色,也就是具备多个角色的权限
	{ role: "<角色名>", db: "<数据库名>" },  // 也可以直接填写由mongoDB内置的角色,例如: "<role>"
	...
    ]
})

注意:

与之前学习的redis或MySQL不同,mongoDB的用户是以数据库为单位来分别建立和管理用户访问控制权限的,每个数据库有属于自己的一个或多个管理员。

管理员可以管理自己的数据库,但是不能直接管理其他数据库,要先在内置数据库admin认证后才可以。

管理员的权限设置包含了2块,分别是角色和权限,由创建用户时通过roles属性进行设置。

内置角色

也叫内建角色,是MongoDB安装以后默认创建提供给我们使用的。

javascript 复制代码
数据库用户角色:read、readWrite
数据库管理角色:dbAdmin、dbOwner、userAdmin
集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager
备份恢复角色:backup、restore
所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase 
超级用户角色:root
// 超级用户角色 提供了系统超级用户的访问权限
// 有几个角色间接或直接提供了系统超级用户的访问权限(dbAdmin、dbOwner 、userAdmin、userAdminAnyDatabase、dbAdminAnyDatabase)
内置权限
Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,擁有超级权限,对所有数据具有超级权限。
给Admin数据库创建账户管理员(相当于给mpongoDB创建了一个HR)

当前账号只能用于管理admin数据库账号,不能进行其他数据库的操作。

javascript 复制代码
// 进入/切换数据库到admin中
use admin
// 创建账户管理员
// [用户名:admin,密码:123456,数据库:admin,角色userAdminAnyDatabase,表示admin用户在admin数据库中具有超级用户权限]
db.createUser({
	user: "admin",
	pwd: "123456",
	roles: [
		{role: "userAdminAnyDatabase",db:"admin"},
	]
});

// 终端效果如下:
Successfully added user: {
	"user" : "admin",
	"roles" : [
		{
			"role" : "userAdminAnyDatabase",
			"db" : "admin"
		}
	]
}
创建超级管理员

当前账号可以进行数据库相关操作。

javascript 复制代码
// 进入/切换数据库到admin中
use admin
// 创建超级管理员账号
db.createUser({
    user: "root",
    pwd: "123456",
    roles: [
    	{role:"root", db:"admin"},  // 也可以这样简写:"root",
    ]
})

// 终端效果如下:
Successfully added user: {
	"user" : "root",
	"roles" : [
		{
			"role" : "root",
			"db" : "admin"
		}
	]
}
创建用户自己的数据库的角色

帐号是跟着数据库绑定的,所以是什么数据库的用户,就必须先到指定库里授权和验证!!!

一般开发中,往往一个项目就分配一个数据库,并给这个数据库分配一个管理员!!!

javascript 复制代码
// 切换数据库,如果当前库不存在则自动创建
use yingmingapp;
// 创建管理员用户,为了保证不会报错,所以先删除同名管理员 db.system.users.remove({user:"yingmingapp"});
db.createUser({
    user: "yingmingapp",
    pwd: "123456",
    roles: [
        { role: "dbOwner", db: "yingmingapp"}
    ]
})

// 终端下的效果
/*
Successfully added user: {
	"user" : "yingmingapp",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "yingmingapp"
		}
	]
}
*/

// 查看当前仓库下的用户
show users;
/* 效果:
{
	"_id" : "yingmingapp.yingmingapp",
	"userId" : UUID("126f2bd2-cddc-450a-9814-3e8937827ab3"),
	"user" : "yingmingapp",
	"db" : "yingmingapp",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "yingmingapp"
		}
	],
	"mechanisms" : [
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}
*/

// 也可以在admin库中全局显示所有用户
use admin
db.system.users.find()
用户信息
查看当前数据库下的管理用户

只需要切换到对应的库中即可查看

javascript 复制代码
use yingmingapp
show users
查看系统中所有的用户

需要切换到admin中使用账号管理员的权限进行操作

javascript 复制代码
use admin
// db.auth("root","123456") // 如果开启了mongoDB的访问控制,就需要下用户认证密码才能继续往下操作
db.system.users.find() // 只能在admin数据库中使用。
// db.system.users.find().pretty()  // 在mongoDB5.0以前的版本中,pretty表示在返回多个查询结果时,以结构化格式显示数据
删除用户

语法格式:

javascript 复制代码
db.system.users.remove(json条件)

例如,删除上面的用户名为yingmingapp的用户。

javascript 复制代码
// 有多种删除方式,下面是根据user用户名删除用户
use admin
db.system.users.deleteOne({user:"yingmingapp"});

// 删除效果:
WriteResult({ "nRemoved" : 1 })  // nRemoved 大于0表示成功删除管理员,等于0则表示没有删除。
修改密码

必须切换到对应的数据库下才能给用户修改密码。

所以针对账户管理员或者超级管理员,需要在admin下修改,而其他数据库管理员则必须到对应数据库下才能修改。

js 复制代码
db.changeUserPassword("账户名", "新密码");

mongo终端操作:

javascript 复制代码
use yingmingapp
// 添加测试账号:
db.createUser({
    user: "yingmingapp",
    pwd: "123456",
    roles: [
        { role: "dbOwner", db: "yingmingapp"}
    ]
})
// 注册必须保证有这个管理员
db.changeUserPassword("yingmingapp", "123");
开启账户认证

开启账户认证功能,必须要修改配置文件,然后重启mongoDB才能生效。

javascript 复制代码
sudo vim /etc/mongod.conf
// 找到31行附近的 security,去掉左边注释符号(#)
security:
    authorization: enabled

:wq
// 重启mongdb,配置生效
sudo systemctl restart mongod

// 开启了账户认证机制以后,再次进入yingmingapp
mongosh
use yingmingapp
show users    // 此处会报错如:uncaught exception: Error: command usersInfo requires authentication
db.auth("yingmingapp","123")   // 此处认证时填写正确密码:
// { ok: 1 }

exit
mongosh
use yingmingapp
db.auth("yingmingapp","123456")  // 此处认证时填写错误密码:
// MongoServerError: Authentication failed.


// 注意:如果实现以某个库的账户管理员登录数据库以后,要切换账号操作其他数据库,则必须先退出当前登录状态。

数据库管理

显示所有数据库列表【空数据库不会显示,或者说空数据库已经被mongoDB回收了。】

javascript 复制代码
show dbs
show databases

切换数据库,如果数据库不存在则创建数据库。

javascript 复制代码
use  <database>

查看当前工作的数据库

javascript 复制代码
db   // 是 db.getName() 的简写

删除当前数据库,如果数据库不存在,也会返回{"ok":1}

javascript 复制代码
use <db>          // 先切换到要删除的数据库中,然后才能删除数据库
db.dropDatabase()

查看当前数据库状态

json 复制代码
test> db.stats()
{
  db: 'test', // 当前数据库名
  collections: 1,  // 当前数据库中的数据集合数量,相当于mysql的数据表
  views: 0, // 当前数据库中的视图数量
  objects: 200000, // 当前数据库中的文档数据,相当于mysql中的数据记录
  avgObjSize: 235.415035,  // 当前数据库中的文档平均大小
  dataSize: 47083007, // 当前数据库中的数据总文件大小
  storageSize: 10096640,  // 存储引擎占据的文件大小
  indexes: 1,   // 当前数据库中的索引数量
  indexSize: 2756608, // 当前数据库中的索引文件大小
  totalSize: 12853248,   // 数据库中总数据总文件大小
  scaleFactor: 1,
  fsUsedSize: 41576349696,  // 文件系统空间占用大小
  fsTotalSize: 51989970944,  // 文件系统的总占用空间大小
  ok: 1
}

在mongoDB中,最重要的核心是文档,如果一个库或者一个库下的集合中的文档全部被删除了,则这个库和这个集合就意味着不存在,会被mongoDB回收删除。

集合管理

mongoDB中的集合有2种:固定集和动态集。

创建集合

一般工作中使用的是动态集,但是在mongoDB优化时,可以对部分数据转换成使用固定集来保存,性能更好,查询速度更快。

在mongodb中如果使用的动态集,其实不需要专门创建集合,直接添加文档,mongodb也会自动生成动态集合的。

而固定集需要设置存储的文档上限数量,所以需要提前创建集合。

javascript 复制代码
// name为必填参数,options为可选参数。capped若设置值为true,则size必须也一并设置
db.createCollection(
	<集合名称>,   //  同一数据库中,集合名是唯一的,不能重复创建,否则报错!
	{ 
		capped : <boolean>,       // 当前创建的集合是否是固定集,固定集指限制固定数据大小的集合,当数据达到最大值会自动覆盖最早添加的文档内容
		size : <bytes_size>,          // 如果capped值为True,则表示创建固定集,需要指定固定集合存储的最大字节数,单位:字节数.
		max : <collection_size>   // 如果capped值为True,则表示创建固定集,需要指定固定集合中包含文档的最大数量,单位:字节数
	}
);

// db.createCollection不填写第二个参数则表示创建的是动态集
// 添加文档到不存在的集合中,mongodb会自动创建动态集合,
// db.<集合名称>.insert({"name":"python入门","price" : 31.4})
db.courses.insert({"name":"python入门","price" : 31.4})	
固定集的使用操作

固定集一般用于日志,历史记录中。固定集,会因为设置了max或size上限而出现文档被驱逐/出列的情况。后面新增超出部分的数据依旧被添加到集合中,但是最早添加的同等数据数据会被删除掉。

使用场景:

记录访问足迹,记录日志(防止日志记录太多)

javascript 复制代码
// 创建固定集记录用户的访问足迹,集合名:history
> db.createCollection("history", {capped:true, size: 1000000, max: 5, });
{ "ok" : 1 }

// 可以通过show tables 直接查看当前数据库中所有的集合列表
> show tables;
// history
// orders

// 添加数据到固定集
test > db.history.insert({"name":"python入门","price" : 31.4})
// WriteResult({ "nInserted" : 1 })
test > db.history.insert({"name":"python入门","price" : 32.4})
// WriteResult({ "nInserted" : 1 })
test > db.history.insert({"name":"python入门","price" : 33.4})
// WriteResult({ "nInserted" : 1 })
test > db.history.insert({"name":"python入门","price" : 34.4})
// WriteResult({ "nInserted" : 1 })
test > db.history.insert({"name":"python入门","price" : 35.4})
// WriteResult({ "nInserted" : 1 })
test > db.history.insert({"name":"python入门","price" : 36.4})
// WriteResult({ "nInserted" : 1 })

// 上面一共添加了6条数据到固定集,当时因为创建固定时设置了只允许5条数据,
// 所以固定集中针对旧的数据已经删除,只保留最新的5数据。
test > db.history.find()
// [
//   {_id: ObjectId("634563f011b6d817d0188edd"), name: 'python入门', price: 32.4 }, 
//   {_id: ObjectId("634563f211b6d817d0188ede"), name: 'python入门', price: 33.4 }, 
//   {_id: ObjectId("634563f511b6d817d0188edf"), name: 'python入门', price: 34.4 },
//   {_id: ObjectId("634563f711b6d817d0188ee0"), name: 'python入门', price: 35.4 },
//   {_id: ObjectId("634563f911b6d817d0188ee1"), name: 'python入门', price: 36.4 }
// ]
集合列表
javascript 复制代码
show collections // 或 show tables   或 db.getCollectionNames()
删除集合

删除集合,那么集合中原有的文档数据也被删除掉。

javascript 复制代码
db.集合.drop()
查看集合
javascript 复制代码
// 获取集合的对象
// db.getCollection("集合名称")   或者 db.集合名称

test> db.getCollection("orders")
test.orders
test> db.orders
test.orders
查看集合状态信息
javascript 复制代码
// db.集合名称.stats()
db.orders.stats()
// 打印效果:
{
  ns: 'test.orders',   // 当前集合的命名空间(namespace)
  size: 47083007,    // 当前集合的数据总字节数
  count: 200000,     // 当前集合的文档数量
  avgObjSize: 235,  // 当前集合的每个文档平均字节数
  capped: false,       // 当前集合是否是固定集
  },
  nindexes: 1,           // 当前集合的索引数量,默认如果没有特定设置,默认mongoDB会给文档添加一个_id作为主键。
  totalIndexSize: 2756608,  // 当前集合的索引的总字节数
  totalSize: 12853248,         // 当前集合的数据总字节数
  indexSizes: { _id_: 2756608 },   // 当前集合的索引的数值上限
}

文档管理

mongodb中,文档也叫 object/document。对应的就是存储的bson数据记录,对应的就是python中的字典或者列表。

mongodb中,允许文档中有各种的自定义字段,每一个字段对应的值也存在不同数据格式,根据不同的格式产生不同的数据类型。

数据类型
Type 描述
ObjectID 用于存储文档的ID,相当于主键,区分文档的唯一字段,mongoDB中就是一个ObjectID对象的返回值。一共由3部分组成:4个字节的时间戳、5个字节的客户端进程生成的随机数、3个字节的增量计数器,一共12字节长度的十六进制数,以此保证了每一个文档的唯一性。
String 字符串是最常用的数据类型,MongoDB中的字符串必须是UTF-8编码。
Integer 整数类型用于存储数值。整数可以是32位,也可以是64位,这取决于你的服务器。
Double 双精度类型,用于存储浮点值,mongodb中没有float浮点数这个说法
Boolean 布尔类型用于存储布尔值(true/ false),注意:是小写的!!!
Arrays 将数组、列表或多个值存储到一个键,[]
Timestamp 时间戳,用于记录文档何时被修改或创建。Date(),Timestamp(),ISODate() ,默认是ISODate()
Date 用于以UNIX时间格式存储当前日期或时间。
Object 用于嵌入文档, 相当于子属性是另一个json文档而已,这种方式就可以实现嵌套。{}
Null 空值,相当于 python的None
Symbol 与字符串用法相同,常用于某些使用特殊符号的语言,可以理解为一种二进制格式字符串
Binary data 二进制数据,常用于保存文件的内容,往往是图片、音频、视频等数据本身。
Code 用于将JavaScript代码存储到文档中
Regular expression 正则表达式

虽然,MongoDB中提供了数据类型,但是mongoDB中对于文档的添加,是不需要预先约束字段值类型的,而是一种自动推断类型。因此,上面的这些类型,我们知道即可。

添加文档

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

BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称。

javascript 复制代码
// 添加文档
// 方式1
db.<集合名称>.insertOne(          // 如果文档存在_id主键为更新数据,否则就添加数据。
   <document>
)

// 方式2:
// 一次性添加多个文档, 多次给同一个集合建议使用insertMany比insertOne效率更好
db.<集合名称>.insertMany([
    <document>,
    <document>,
    ...
])

操作:

javascript 复制代码
use test;

// 添加一条数据[insert是过去版本的MongoDB提供的添加数据方法]
db.users.insertOne({
    "name": "laoli",  // string
    "age": 33,         // integer
    "sex": true,       // boolean
    "child": {           // Object
        "name":"xiaohuihui",  // string
        "age":6    // integer
    }
});
// WriteResult({ "nInserted" : 1 })


// mongoDB原则上内置了js解释引擎,所以支持js语法
> db.users.findOne()._id
// ObjectId("61552b913ccd8ec29dbf6512")
> db.users.findOne().name
// laoli

// javascrit总可以通过typeof 来查看数据的类型
> typeof db.users.findOne().name
// string
> typeof db.users.findOne()._id
// object
> typeof db.users.findOne().sex
// boolean
> typeof db.users.findOne().age
// number
> typeof db.users.findOne().child
// object
> typeof db.users.findOne().child.name
// string


// 添加一条数据
db.users.insertOne({"name":"xiaozhang","age":18,"sex":true, "money": 300.50});
// {
// 	"acknowledged" : true,
// 	"insertedId" : ObjectId("605021e6d5c7a55cc95c1cb7")
// }


// 添加多条数据
db.users.insertMany([
    {"name":"xiaolan","age":16},
    {"name":"xiaoguang","age":16}
]);

// {
// 	"acknowledged" : true,
// 	"insertedIds" : [
// 		ObjectId("60502235d5c7a55cc95c1cba"),
// 		ObjectId("60502235d5c7a55cc95c1cbb")
// 	]
// }
db.users.find()
删除文档
json 复制代码
// 方式1: 删除一条数据
db.<集合名称>.deleteOne(
   <filter>,  // removed的条件表达式,一般写法:{"字段":{查询器函数:值}},如果不填写删除条件,删除所有文档
)

// 方式2:删除多条数据
db.<集合名称>.deleteMany(
   <filter>,  // removed的条件表达式,一般写法:{"属性":{条件:值}},如果不填写条件,删除所有文档
)

操作:

javascript 复制代码
// 添加多条测试数据
document1 = {"name":"xiaohei","age":16}
document2 = {"name":"xiaobai","age":15}
document3 = {"name":"xiaolan","age":18}
document4 = {"name":"xiaohui","age":11}
document5 = {"name":"xiaoming","age":13}
db.users.insertMany([document1,document2,document3,document4,document5]);

// 删除满足条件的第一条数据
// mongoDB中的条件格式: {字段名:{$运算符:值}
// 条件:{"age":{$eq:16}}   相当于SQL语句的age=16
 db.users.find({"age":{$eq:16}})
db.users.deleteOne({"age":{$eq:16}})
db.users.deleteOne({"age":16}); // 等于可以省略不写,相当于 db.users.remove({"age":{$eq:16}});


// 再次 添加多条测试数据
db.users.insertOne({"name": "laozhang", "age": 33, "sex": true, child: {name: "xiaozhang", "age": 4}})
db.users.insertOne({"name": "laowang", "age": 32, "sex": true, child: {name: "xiaowang", "age": 2}})
db.users.insertOne({"name": "laoli", "age": 33, "sex": true, child: {name: "xiaoli", "age": 1}})
    db.users.insertOne({"name": "laosun", "age": 34, "sex": true, child: {name: "xiaosun", "age": 7}})
    db.users.insertOne({"name": "laozhao", "age": 32, "sex": true, child: {name: "xiaozhao", "age": 6}})
    db.users.insertOne({"name": "laoyang", "age": 35, "sex": true, child: {name: "xiaoyang", "age": 4}})
db.users.insertOne({"name": "laohuang", "age": 36, "sex": true, child: {name: "xiaohuang", "age": 3}})

// 删除一条
db.users.deleteOne({"child.age": {$lte: 2}})  // 删除1条 孩子年龄小于或等于2岁的家长数据,

// 删除多条
db.users.deleteMany({"child.age": {$lt: 5}})  // 把所有孩子年龄小于5岁的家长数据全部删除

mongoDB中的条件,不仅用于删除数据的条件,也可以是查询或者更新的过滤条件。

$eq $lte $gte

查询文档
json 复制代码
// 直接显示查询的所有,find和findOne的第二个参数,也是一个json对象,一般称之为字段投影,表示设置是否显示或隐藏指定数据字段。

// 获取一条
db.集合.findOne(
    <query>,     // 查询条件,删除、查询、修改都需要设置条件、条件写法基本一样的。
    {                      // 查询结果的字段投影,用于指定查询结果以后,显示的字段列
    	<field>: 0, // 隐藏指定字段,例如:"_id":0,
    	<field>: 1, // 显示指定字段,例如:"title":1,
    	....
    }
)

// 获取多条
db.集合.find(
	<query>,      // 查询条件
    {
    	<key>: 0, // 隐藏指定字段,例如:"_id":0,
    	<key>: 1, // 显示指定字段,例如:"title":1,
    	....
    }
)

// 以易读的方式来格式化显示读取到的数据,只能在find方法后面使用。mongoDB6.0版本以后。默认以易读的方式来格式化显示
db.集合.find().pretty()

操作:

javascript 复制代码
// 切换数据库
use yingmingapp;

// 查询整个集合的所有数据
db.users.find()
db.users.find({})  // 没有设置任何条件

// 查询一条数据
db.users.findOne()    // 获取集合中第一条数据
db.users.findOne({})  // 同上

// 设置字段投影,显示结果中字段列
db.users.find({}, {"child": 0})    // 获取集合中所有数据,并隐藏child属性的数据
db.users.find({}, {"_id": 0, "child": 0})    // 获取集合中所有数据,并隐藏_id 与 child属性的数据
db.users.find({}, {"name": 1})    //  获取集合中所有数据,并只显示name属性与_id主键的字段数据[注意:_id如果不明确隐藏,默认显示的]
db.users.find({}, {"name": 1, "_id": 0})    //  获取集合中所有数据,并只显示name属性的字段数据
条件运算符

mongoDB中,条件运算符也叫查询器(query selector)

比较运算
操作 格式 语法例子 SQL中的类似语句
等于 写法1:{<key>:<val>} 写法2:{<key>:{$eq:<val>}} db.集合.find({"name":"xiaoming"}) where name = 'xiaoming'
小于 {<key>:{$lt:<val>}} db.集合.find({"age":{$lt:17}}) where age < 17
小于或等于 {<key>:{$lte:<val>}} db.集合.find({"age":{$lte:17}}) where age <= 17
大于 {<key>:{$gt:<val>}} db.集合.find({"age":{$gt:17}}) where age > 17
大于或等于 {<key>:{$gte:<val>}} db.集合.find({"age":{$gte:17}}) where age >= 17
不等于 {<key>:{$ne:<val>}} db.集合.find({"age":{$ne:17}}) where age != 17
包含 {<key>:{$in:[<val>...]}} db.集合.find({"age":{$in:[1,2,3]}}) where age in (1,2,3)
不包含 {<key>:{$nin:[<val>...]}} db.集合.find({"age":{$nin:[1,2,3]}}) where age not in (1,2,3)

终端运行

javascript 复制代码
// 添加测试数据
db.users.insertOne({"name": "laozhang", "age": 33, "sex": true, child: {name: "xiaozhang", "age": 4}})
db.users.insertOne({"name": "laowang", "age": 32, "sex": true, child: {name: "xiaowang", "age": 2}})
db.users.insertOne({"name": "laoli", "age": 33, "sex": true, child: {name: "xiaoli", "age": 1}})
db.users.insertOne({"name": "laosun", "age": 34, "sex": true, child: {name: "xiaosun", "age": 7}})
db.users.insertOne({"name": "laozhao", "age": 32, "sex": true, child: {name: "xiaozhao", "age": 6}})
db.users.insertOne({"name": "laoyang", "age": 35, "sex": true, child: {name: "xiaoyang", "age": 4}})
db.users.insertOne({"name": "laohuang", "age": 36, "sex": true, child: {name: "xiaohuang", "age": 3}})
db.users.insertOne({"name": "zhangsanfeng"})

// 查询年龄大于33岁的用户信息
db.users.find({"age":{$gt:33}})
// 查询年龄小于33岁的用户信息
db.users.find({"age":{$lt:33}})
// 查询年龄在32,33,34岁范围里面用户信息
db.users.find({"age":{$in:[32,33,34]}})
// 注意:没有当前字段的文档是不会被通过大于、小于或等于查询出来,因为默认条件不成立
// 所以依靠不存在的字段,是无法使用比较运算符查询出上面添加zhangsanfeng的
db.users.find({"age":{$lt:33}})
db.users.find({"age":{$gte:33}})
db.users.find({"age":{$in:[33]}})
db.users.find({"age":{$nin:[33]}})  // 使用排除范围的方式,可以查询出来。
db.users.find({"age":{$ne: 33}})    // 使用不等于的方式,可以查询出来。
逻辑运算
操作 语法 语法例子
$and 写法1:{<key>:<val>,<key>:<val>,...} 写法2:{$and: [{key:{$运算符:<val>}},....]} db.集合.find({key1:value1, key2:value2})
$or {$or: [{<key>: {$运算符:<val>}}, ....]} db.集合.find({$or: [{key1: value1}, {key2:value2}]})
$and$or组合使用 写法1:{<key>:<val>, $or: [{<key>: {<$运算符>:<val>}},...]} 写法2:{$and:[{$or:[{<key>:{<$运算符>:<val>}},..]},$or:[{<key>:{<$运算符>:<val>}},..]}]} db.集合.find({key1:value1, $or: [{key1: value1}, {key2:value2}]})
$not {<key>:{$not:{<$运算符>:<val>}}} db.集合.find({key1:{KaTeX parse error: Expected '}', got 'EOF' at end of input: not:{运算符: val1}}})
javascript 复制代码
SQL:
	(class=301 and sex =true)  or (class=302 and sex=false)
mongo:
	{
            $or: [
        	{$and: [{class:301}, {sex:true}]},
        	{$and: [{class:302}, {sex:false}]},
            ]
       }

$and,终端操作:

javascript 复制代码
// 查询age>34 并且 child.age<5
db.users.find({
    $and:[
        {"age":{$gt:34}},
        {"child.age":{$lt: 5}}
    ]
})
// 简写;
db.users.find({"age": {$gt:34}, "child.age": {$lt:5}})

// 查詢age=35,child.age=4
db.users.find({
    $and:[
        {"age":{$eq:35}},
        {"child.age":{$eq: 4}}
    ]
})

// 简写
db.users.find({
    $and:[
        {"age": 35},
        {"child.age": 4}
    ]
})

// 再次简写:
db.users.find({
        "age": 35, 
        "child.age": 4,
})

o r 、 or、 or、and与$or的组合使用,终端操作:

javascript 复制代码
// 查询age=33或者age=36
db.users.find({
    $or: [
        {"age":{$eq:33}},
        {"age":{$eq:36}}
    ]
})
// 简写:
db.users.find({
    $or:[
        {"age":33},
        {"age":36}
    ]
})

// 查询age=33,child.age==5 或者 age=35,child.age==4
db.users.find({
    $or: [
        {$and: [{age: {$eq: 33}}, {"child.age": {$eq: 5}}]},
        {$and: [{age: {$eq: 35}}, {"child.age": {$eq: 4}}]},
    ]
})

// 简写:
db.users.find({
    $or: [
        {$and: [{age: 33}, {"child.age": 5}]},
        {$and: [{age: 35}, {"child.age": 4}]},
    ]
})

// 再次简写:
db.users.find({
    $or: [
        {age: 33, "child.age": 5},
        {age: 35, "child.age": 4},
    ]
})

$not,终端操作:

javascript 复制代码
// 查询年龄!=16的
db.users.find({"age":{$not:{$eq:33}}})
// 简写:
db.users.find({"age":{$ne: 33}})
其他运算符
操作 格式 语法例子 说明
$type {<key>:{$type: <datetype>}} db.集合.find({"name":{$type:'string'}}) 匹配指定键是指定数据类型的文档 number 数值型 string 字符串 bool 布尔类型 object json文档对象类型 array 数组类型
$exists {<key>:{$exists:<bool>} db.集合.find({"title":{$exists:true}}) 匹配具有指定键的文档,存在指定字段的文档
$regex { <key>:/模式/<修正符>} {<key>:{$regex:/模式/<修正符>}} db.集合.find({"name":{$regex:/张$/}}) 按正则匹配
$mod {<key>: {$mod: [除数, 余数]}} db.集合.find({"age":{$mod:[10,0]}}) 算数运算,取模,语法中举例是age除以10==0

终端操作:

javascript 复制代码
db.users.insert({"name":"xiaoming","sex":0,"age":"18", "mobile": "13313621234"});
db.users.insert({"name":"xiaoming","sex":1,"age":18, "mobile": "13441351234"});
db.users.insert({"name":"xiaoming","sex":1,"age":33, "mobile": "13851351234"});
db.users.insert({"name":"xiaoming","sex":0,"age":"33", "mobile": "13881351234", child: {"age": 3, "sex": true}});
db.users.insert({"name":"xiaoming","sex":1,"age":33, "mobile": "10086"});
// $type的使用
db.users.find({"sex":{$type:"number"}});
db.users.find({"sex":{$type:"bool"}});
db.users.find({"age":{$type:"string"}});


// $exists
db.users.find({"child":{$exists:true}}); // 查询出存在child字段的数据

// $regex 正则匹配
db.users.find({"mobile":{$regex: /^133/ }});
// 不符合手机号码格式的
db.users.find({"mobile":{$not:{$regex: /1[3-9]\d{9}/ }}});


// $mod 取模,注意:仅针对数值类型,对于字符串是不识别的,所以$mod[3,0],对于 "18"来说,并非整除!
db.users.find({"age":{$mod: [3,0] }});
自定义条件查询函数

慎用!效率差,因为需要额外调用javascript执行引擎才过滤,相对效率差。

json 复制代码
// 用法1,逻辑比较复杂的情况,可以使用更多的javascript进行运算处理:结果函数结果为true,则当前数据被查询出来。
db.<集合名称>.find({$where: function(){   // this代表的就是查询过程中,被循环的每一条文档数据
    return <this.字段> <运算符> <条件值>;
}}});

// 用法2,相对没那么复杂的,取函数的返回值作为条件值:
db.集合.find({$where: "<this.字段> <运算符> <条件值>"});
// db.集合.find({$where:"this.name=='xiaoming'"});

操作:

javascript 复制代码
db.users.find({$where: function(){
    return this.age>33 && this.child.age<4;
}});

// 把字符串作为代码条件执行,当结果为true,则返回当前符合的数据
db.users.find({$where: "this.age>33 && this.child.age<4"});
排序显示
json 复制代码
db.集合.find().sort({<key>:1})  // 升序,默认为升序
db.集合.find().sort({<key>:-1}) // 倒序, 

终端操作:

javascript 复制代码
db.users.find().sort({age:-1});
db.users.find().sort({age:-1, sex:1});
字段投影

find()方法默认将返回文档的所有数据,但是可以通过设置find()的第二个参数projection,设置值查询部分数据。

语法:

json 复制代码
// 获取一条
db.集合.findOne(
	<query>,     // 查询条件
    {
    	<key>: 0, // 隐藏指定字段,例如:"_id":0,
    	<key>: 1, // 显示指定字段,例如:"title":1,
    	....
    }
)
// 获取多条
db.集合.find(
	<query>,      // 查询条件
    {
    	<key>: 0, // 隐藏指定字段,例如:"_id":0,
    	<key>: 1, // 显示指定字段,例如:"title":1,
    	....
    }
)

操作:

javascript 复制代码
> db.users.find({"mobile":{$regex:/^133\d{8}$/}},{"_id":0}).sort({"mobile":-1})
    { "name" : "xiaoming", "mobile" : "13333355678" }
    { "name" : "xiaoming", "mobile" : "13333345678" }
    { "name" : "xiaoming", "mobile" : "13312345678" }

> db.users.find({"mobile":{$regex:/^133\d{8}$/}},{"_id":0,"name":0}).sort({"mobile":-1})
    { "mobile" : "13333355678" }
    { "mobile" : "13333345678" }
    { "mobile" : "13312345678" }

> db.users.find({"mobile":{$regex:/^133\d{8}$/}},{"name":1}).sort({"mobile":-1})
    { "_id" : ObjectId("60502fb7d5c7a55cc95c1cc4"), "name" : "xiaoming" }
    { "_id" : ObjectId("60502fb4d5c7a55cc95c1cc3"), "name" : "xiaoming" }
    { "_id" : ObjectId("60502fb1d5c7a55cc95c1cc2"), "name" : "xiaoming" }

> db.users.find({"mobile":{$regex:/^133\d{8}$/}},{"name":1,"_id":0}).sort({"mobile":-1})
    { "name" : "xiaoming" }
    { "name" : "xiaoming" }
    { "name" : "xiaoming" }
限制与偏移

limit方法用于限制返回结果的数量

skip方法用于设置返回结果的开始位置

json 复制代码
db.集合.find(...).limit(结果数量).skip(开始下标)

终端操作:

javascript 复制代码
db.users.find({},{"_id":0,"name":1,"age":1}).sort({"age":1}).limit(5);
db.users.find({},{"_id":0,"name":1,"age":1}).sort({"age":1}).limit(5).skip(0);

db.users.find({},{"_id":0,"name":1,"age":1}).sort({"age":1}).limit(5).skip(5);
更新文档
json 复制代码
// 更新数据
db.集合.update(
    <query>,
    <update>,
    {
     upsert: <boolean>, // 可选参数,如果文档不存在,是否插入objNew, true为插入,默认是false,不插入
     multi: <boolean>,  // 可选参数,是否把满足条件的所有数据全部更新,设置更新1条还是多条
     writeConcern: <document> // 可选参数,抛出异常的级别。
   }
)

// 更新一条
db.集合.updateOne(
   <query>,   // update的查询条件,一般写法:{"属性":{条件:值}}
   <update>,  // update的更新数据,一般写法 { $set:{"属性":"值",....} } 或者 { $inc:{"属性":"值"} }
   {
     upsert: <boolean>, // 可选参数,如果文档不存在,是否插入objNew, true为插入,默认是false,不插入
     multi: <boolean>,  // 可选参数,是否把满足条件的所有数据全部更新,设置更新1条还是多条
     writeConcern: <document> // 可选参数,抛出异常的级别。
   }
)

// 更 新多条
db.集合.updateMany(
   <query>,   // update的查询条件,一般写法:{"属性":{条件:值}}
   <update>,  // update的对象,一般写法 { $set:{"属性":"值"} } 或者 { $inc:{"属性":"值"} }
   {
     upsert: <boolean>, // 可选参数,如果文档不存在,是否插入objNew, true为插入,默认是false,不插入
     multi: <boolean>,  // 可选参数,是否把满足条件的所有数据全部更新
     writeConcern: <document> // 可选参数,抛出异常的级别。
   }
)
update更新运算符[修改器]
操作 语法
$inc db.集合.update({<key1>:<val1>},{$inc:{<key2>:<val2>}}) 更新key1=val1的文档中key2的值为val2,类似python的递增递减 递减,则{ $inc:{<key2>:-<val2>} }
$set db.集合.update({<key1>:<val>}, {$set:{<key2>:<val2>}}) 更新key1=val1的文档中key2的值为val2,如果key2不存在则新增对应键值对
$unset db.集合.update({<key1>:<val>}, {$unset:{<key2>:<val2>}}) 移除key1=val1的文档中key2=val2这个键值对
$push db.集合.update({<key1>:<val>}, {$push:{<key2>:<val2>}}) 给key1=val1的文档中key2列表增加1个数组成员val2。 key2必须是数组。
$pull db.集合.update({<key1>:<val>}, {$pull:{<key2>:<val2>}}) 与push相反,给key1=val1的文档中key2列表删除1个指定成员val2
$pullAll db.集合.update({:}, {$pullAll:{:[,]}}) 与$pull作用一样,用于删除多个指定成员
$pop db.集合.update({<key1>:<val>}, {$pop:{<key2>:<val2>}}) 给key1=val1的文档中key2列表移除第一个或最后一个成员。 val2只能是1(最后面)或-1(最前面),与python相反

终端操作:

javascript 复制代码
// $inc
// 把laoli的年龄+10岁
db.users.update({"name":"laoli"},{$inc:{"age":10}}); // 更新一条
db.users.updateMany({"name":"xiaoming"},{$inc:{"age":10}}); // 更新多条
// 把laoli的孩子年龄+10岁
db.users.update({"name":"laoli"},{$inc:{"child.age":10}});


// $set
// 如果字段不存在,则新增字段的键值对,如果字段存在,则修改字段的值
//更新laoli的手机号码
db.users.update({"name":"laoli"},{$set:{"mobile":"18012312312"}}); // 更新一条
// 更新laoli孩子的手机号码
db.users.update({"name":"laoli"},{$set:{"child.mobile":"18012312312"}});

// $unset
// 移除laoli的性别键值对
db.users.update({"name":"laoli"},{$unset:{"sex":true}});

// $push
db.users.update({"name":"laoli"},{$set:{"lve":["TV","game"]}});
db.users.update({"name":"laoli"},{$push:{"lve":"code"}}); // 往列表属性中追加成员

// $addToSet 结合 $each 把一个数组中每一个成员添加到数组中
db.users.update({"name":"laoli"},{$addToSet:{"lve":{$each:["code","music","TV"]}}});

// $pull
db.users.update({"name":"laoli"},{$pull:{"lve":"TV"}});

// $pullAll
db.users.update({"name":"laoli"},{$pullAll:{"lve":["TV","game"]}});


// $pop
db.users.update({"name":"laoli"},{$pop:{"lve":-1}}); // 左边移除列表的第一个成员
db.users.update({"name":"laoli"},{$pop:{"lve":1}}); // 右边移除列表的最后一个成员

// $rename 字段名重命名
db.users.update({"name":"laoli"},{$rename:{"lve":"love"}});

索引操作

前面学习过MySQL,我们知道数据库里给数据构建索引通常能够极大的提高数据查询的效率,缩短查询耗时,如果没有索引,数据库在查询数据时必然会扫描数据表中的每个记录并提取那些符合查询条件的记录。同理,在MongoDB中构建索引也可以提高数据的查询效率和缩短查询耗时,没有索引的情况也是一样,MongoDB也会再查询数据时扫描集合中的每个文档并提取符合查询条件的文档。这种扫描全集合的查询效率是无疑是非常低下的,特别在处理大量的集合数据时,查询时间可能会达到几十秒甚至几分钟,这对用户体验来说是非常致命的。

文档:

准备数据
javascript 复制代码
use demo
fotmatnumber = (start, end)=>{
    num = Math.round(Math.random() * (end-start)) + start
    if(num<10){
        return "0"+num;
    }else{
        return num;
    }
}

rand_title = (i)=>{
    num = Math.round( Math.random() * 10 );
    num1 = Math.round( Math.random() * 10 );
    return [
        "赠送礼品-"+num,
        "购物狂欢-"+num,
        "随便买买-"+num,
        "愉快购物-"+num,
        "赠送礼物-"+num,
        "商品购买-"+num,
        "买多送多-"+num,
        "买年货-"+num,
        "买买买买-"+num,
        "充值会员-"+num
    ][num1];
}

for(var i=0; i<200000; i++){  
    db.orders.insert({
        "onumber": ( "0000000000000000" + i ).substr( String(i).length ),  
        "date": "20"+fotmatnumber(0,21)+"-"+fotmatnumber(1,12)+"-"+fotmatnumber(1,31),  
        "title": rand_title(i),
        "user_id": parseInt(i/200)+1,
        "items" :[{ 
        	"goods_id" : parseInt(i/200)+1,
        	"goods_attr" : i,  
        	"price" : 100.0
        },{ 
        	"goods_id" : parseInt(i/200)+2,
        	"goods_attr" : i+1,  
        	"price" : 80.0
        }]
    })
    if(i%10000==0){
        print("已经添加了"+parseInt(i/10000)+"万条数据!");
    }
}
注意事项
  1. MongoDB的索引是存储在运行内存(RAM)中的,所以必须确保索引的大小不超过内存的限制。

    如果索引的大小超过了运行内存的限制,MongoDB会删除一些索引(涉及到mongodb的驱逐机制,这将导致性能下降)

  2. MongoDB的索引在部分查询条件下是不会生效的。

    • 正则表达式及非操作符,如 $nin,$not , 等。
    • 算术运算符,如 $mod, 等。
    • $where自定义查询函数。
    • ...
  3. 索引会在写入数据(添加、更新和删除)时重排,如果项目如果是写多读少,则建议少使用或者不要使用索引。

  4. 一个集合中索引数量不能超过64个。

  5. 索引名的长度不能超过128个字符。

  6. 一个复合索引最多可以有31个字段。

  7. mongodb索引统一在system.indexes集合中管理。这个集合只能通过createIndexdropIndexes来操作。

查看索引
javascript 复制代码
// 获取当前集合中已经创建的所有索引信息
db.集合.getIndexes()
/*
[{ 
	"v" : 2,   // 索引版本
	"key" : {  // 索引的字段及排序方向(1表示升序,-1表示降序)
		"_id" : 1   // 根据_id字段升序索引
    }, 
    "name" : "_id"   // 索引的名称
}]
*/
// 获取当前集合中已经创建的索引总大小,以字节为单位返回结果
db.集合.totalIndexSize()
// 获取当前数据库中所有的索引【不会显示默认主键_id】
db.system.indexes.find()

MongoDB会为插入的文档默认生成_id字段(如果文档本身没有指定该字段),_id是文档唯一的主键,为了保证能根据文档id快速查询文档,MongoDB默认会为集合创建_id字段的主键索引。

查询分析

与SQL语句类似,MongoDB也提供了一个explain,供开发者进行查询分析,优化查询语句。

explain的使用有3个参数,分别是:queryPlanner、executionStats、allPlansExecution,默认是queryPlanner,开发中常用的是executionStat s。

db.。。。.。。。.explain("executionStats");

javascript 复制代码
db.orders.find({"title":"愉快购物-6"}).explain("executionStats");
/*
{
	"queryPlanner" : {  # 被查询优化器选择出来的查询计划
		"plannerVersion" : 1,  # 查询计划版本
		"namespace" : "test.orders", # 要查询的集合
		"indexFilterSet" : false,  # 是否了使用索引
		"parsedQuery" : {  # 查询条件
			"title" : {
				"$eq" : "购买商品-19"
			}
		},
		"winningPlan" : {     # 最佳执行计划
			"stage" : "COLLSCAN", # 扫描类型/扫描阶段
			"filter" : {     # 过滤条件
				"title" : {
					"$eq" : "购买商品-19"
				}
			},
			"direction" : "forward"  # 查询方向,forward为升序,backward表示倒序。
		},
		"rejectedPlans" : [ ]   # 拒绝的执行计划
	},
	"executionStats" : {  # 最佳执行计划的一些统计信息
		"executionSuccess" : true,  # 是否执行成功
		"nReturned" : 1,   # 返回的结果数
		"executionTimeMillis" : 346,  # 执行耗时
		"totalKeysExamined" : 0,      # 索引扫描次数
		"totalDocsExamined" : 1000000,  # 文档扫描次数,所谓的优化无非是让totalDocsExamined和nReturned的值接近。
		"executionStages" : {     # 执行状态
			"stage" : "COLLSCAN",  # 扫描方式/扫描阶段
			"filter" : {
				"title" : {
					"$eq" : "购买商品-19"
				}
			},
			"nReturned" : 1,   # 返回的结果数
			"executionTimeMillisEstimate" : 5,   # 预估耗时
			"works" : 1000002,   # 工作单元数
			"advanced" : 1,      # 优先返回的结果数
			"needTime" : 1000000,
			"needYield" : 0,
			"saveState" : 1000,
			"restoreState" : 1000,
			"isEOF" : 1,
			"direction" : "forward",
			"docsExamined" : 1000000   # 文档检查数目,与totalDocsExamined一致
		}
	},
	"serverInfo" : {   # 服务器信息
		"host" : "ubuntu",
		"port" : 27017,
		"version" : "4.4.2",
		"gitVersion" : "15e73dc5738d2278b688f8929aee605fe4279b0e"
	},
	"ok" : 1
}

*/

stage的扫描类型:

类型名称 描述 期望
COLLSCAN 全表扫描 False
IXSCAN 索引扫描 True
FETCH 根据索引去检索指定document True
IDHACK 针对_id进行查询 True
COUNTSCAN count不使用Index进行count时返回 False
COUNT_SCAN count使用了Index进行count时返回 True
SUBPLA 未使用到索引的$or查询时返回 False
TEXT 使用全文索引进行查询时返回 -
SORT 使用sort排序但是无index时返回 False
SKIP 使用skip跳过但是无index时返回 False
PROJECTION 使用limit限定结果但是无index时返回 False
创建索引

MongoDB支持多种类型的索引,包括普通索引(单列索引)、复合索引、多列索引、全文索引、哈希索引地理位置索引等,每种类型的索引有不同的使用场合。

ttl索引本质上就是普通索引,只是给索引添加一个过期时间而已。

另外,MongoDB的全文索引很弱智,如果真要用在开发中,还是建议使用elasticsearch或者Sphinx。

复合索引注意点

  1. 创建的复合索引对单字段条件的查找没有帮助

  2. 靠左匹配原则:

    如果创建复合索引时,顺序为,name,number,address

    javascript 复制代码
    db.集合.createIndex({
        "name": 1,
        "number": 1,
        "address": 1
     }

    那么在查询时

    1. 全字段匹配(name,number,address三者都在查询时使用),走索引
    2. 单独name查询,也走索引
    3. name,number或者name,address都走索引
    4. number,addresshu或者单独number或者单独address:都不走索引
json 复制代码
// 创建索引
db.集合.createIndex({
    // 单个字段,则为普通索引,    // sort的值表示排序,值为1表示升序索引,-1表示降序索引
    "字段名1": <sort|type>,       // type的值可以是text,表示创建全文索引。db.集合.find({$text:{$search:"字符串"}})
    "字段名2": <sort|type>,       // 多个字段,则为复合索引
    "字段名3": [<值1>,<值2>,...],  // 多列索引
    ....
}, {
    background: <Boolean>,   // 建索引过程会阻塞数据库的其它操作,background可指定以后台方式创建索引,默认为false
    unique: <Boolean>,  // 是否建立唯一索引,默认值为false,也叫唯一索引
    name: <String>,   // 索引的名称,不填写,则MongoDB会通过连接索引的字段名和排序顺序生成一个索引名称 
    expireAfterSeconds: <integer>, // 设置索引的过期时间,类似redis的expire,也叫TTL索引
    sparse: <Boolean>,  // 对文档中不存在的字段数据是否不启用索引,默认为False
});


// 单字段索引[普通索引]
 db.集合.createIndex({
    "字段名": <sort>,    // sort的值表示排序,值为1表示升序索引,-1表示降序索引
 }, {
	....
 })
// 普通索引创建: db.orders.createIndex({"title":1})
// 查询基本使用: db.orders.find({"title":"愉快购物-6"}).explain("executionStats");


// 多字段索引,也叫复合索引。[类似mysql里面的联合索引]
 db.集合.createIndex({
    "字段名1": <sort>,    // sort的值表示排序,值为1表示升序索引,-1表示降序索引
    "字段名2": <sort>,    // sort的值表示排序,值为1表示升序索引,-1表示降序索引
 }, {
	....
 })

// 复合索引的使用对单字段条件的查找是没有帮助的,必须多字段[必须包含复合索引的字段]条件使用
// 复合索引创建:db.orders.createIndex({"date":1,"title":1});
// 查询基本使用:
//     db.orders.find({"date":"2014-06-12","title":"买年货-7"}).explain("executionStats");
//     db.orders.find({"date":"2014-06-12","onumber":"0000000000030014","title":"买年货-7"});


// 全文索引
 db.集合.createIndex({
    "字段名1": "text",    // type的值只能是text,表示创建全文索引。db.集合.find({$text:{$search:"字符串"}})
 }, {
	....
 })

// 全文索引创建: db.orders.createIndex({"title":"text"})
// 查询基本使用: db.orders.find({$text:{$search:"商品-19"}}).explain("executionStats")



// 多列索引[应用的地方是在列表属性]
 db.集合.createIndex({
    "字段名3": [<值1>,<值2>,...],
 }, {
	....
 });

// 创建测试数据
db.doc.drop()
db.doc.insert({"title":"标题1","tags":["python","django"]})
db.doc.insert({"title":"标题1","tags":["python","django"]})
db.doc.insert({"title":"标题1","tags":["python","django"]})
db.doc.insert({"title":"标题2","tags":["java","mvp"]})
db.doc.insert({"title":"标题3","tags":["java","mvp"]})
db.doc.insert({"title":"标题2","tags":["java","mvp"]})
db.doc.insert({"title":"标题3","tags":["python"]})
db.doc.insert({"title":"标题4","tags":["python"]})
db.doc.insert({"title":"标题2","tags":["python","flask"]})
db.doc.insert({"title":"标题3","tags":["java"]})
// 创建多列索引: db.doc.createIndex({"tags":1})
// 查询数据: db.doc.find({"tags":["python"]}).explain("executionStats")



// 唯一索引
db.集合.createIndex({
    "字段名1": <sort>,
}, {
    unique: true,     // 是否建立唯一索引,默认值为false,也叫唯一索引
})
// 创建唯一索引: db.orders.createIndex({"onumber":1},{unique:true});
// 查询数据: db.orders.find({"onumber":"0000000000001019"}).explain("executionStats")



// ttl索引
// 使用ttl索引,索引关键字段的值类型必须是Date类型,如果该字段不是date类型或者文档中不存在该字段,则文档不会进行过期处理
// 数据过期的删除工作是在mongoDB中的独立线程内执行的,默认平均60s扫描一次,不会立即删除。

// 例如:在文档创建10秒后删除文档
db.orders.dropIndex("date_1")
db.orders.createIndex({"date": 1},{expireAfterSeconds: 10});
db.orders.insertOne({
   "date": new Date("2022-01-10 17:30:00"), // 在python中需要通过 utctime
   "user_id": 2,
   "username": "xiaohong"
})

// 在文档创建后,由索引字段值指定的时间删除文档
// 创建索引:db.tasks.createIndex({"expire_time":1},{expireAfterSeconds:0})
// 创建测试数据
db.tasks.insert( {
   "expire_time": new Date('2025-01-10 17:32:05'), // 在python中需要通过 utctime
   "user_id": 2,
   "username": "xiaoming",
   "onumber": "200003"
});

db.tasks.insert( {
   "expire_time": new Date('2025-01-10 17:34:05'), // 在python中需要通过 utctime
   "user_id": 2,
   "username": "xiaoming"
   "onumber": "200004"
});
db.tasks.insert( {
   "expire_time": new Date('2025-01-10 17:35:10'), // 在python中需要通过 utctime
   "user_id": 2,
   "username": "xiaoming"
   "onumber": "200005"
});


db.tasks.insert( {
   "expire_time": new Date('2024-10-7 17:03:30'), 
   "user_id": 20,
   "username": "xiaoduan",
   "onumber": "200006"
});

// 重建索引[一般是在长期项目运行下来,索引创建时间太久了,性能下降的时候使用。]
// !!!!不能在高峰期时运行以下操作
db.集合.reIndex();
删除索引

MongoDB给文档主键_id默认创建单字段索引是无法删除的。

json 复制代码
// 删除单个索引
db.集合.dropIndex("索引名称")
// db.orders.dropIndex("date_1")

// 删除所有索引,慎用
db.集合.dropIndexes()

若有错误与不足请指出,关注DPT一起进步吧!!!

相关推荐
码农老起28 分钟前
企业如何通过TDSQL实现高效数据库迁移与性能优化
数据库·性能优化
夏木~1 小时前
Oracle 中什么情况下 可以使用 EXISTS 替代 IN 提高查询效率
数据库·oracle
W21552 小时前
Liunx下MySQL:表的约束
数据库·mysql
黄名富2 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
言、雲2 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
一个程序员_zhangzhen2 小时前
sqlserver新建用户并分配对视图的只读权限
数据库·sqlserver
zfj3213 小时前
学技术学英文:代码中的锁:悲观锁和乐观锁
数据库·乐观锁··悲观锁·竞态条件
吴冰_hogan3 小时前
MySQL InnoDB 存储引擎 Redo Log(重做日志)详解
数据库·oracle
nbsaas-boot3 小时前
探索 JSON 数据在关系型数据库中的应用:MySQL 与 SQL Server 的对比
数据库·mysql·json
cmdch20173 小时前
Mybatis加密解密查询操作(sql前),where要传入加密后的字段时遇到的问题
数据库·sql·mybatis