MongoDB 入门与常用操作
本文整合了 MongoDB 的入门笔记:先理清文档、集合、数据库三级概念,再在 mongo shell 中完整走一遍增删改查,随后给出对照 SQL 的常用命令速查表,最后用 Python 的 pymongo 完成同样的操作,并附上批量生成测试数据的脚本。
1. 概念与安装
MongoDB 是文档型数据库,数据按「文档 → 集合 → 数据库」三级组织,理解这三个概念之后,后面的命令基本都能对号入座。
文档
- 形如
{"foo": 3, "greeting": "Hello world!"}的一组键值对,是 MongoDB 中最小的数据单元 - 区分大小写
- 键在同一文档内唯一
- 值可以继续嵌套文档
- 键值对是有序的
文档相当于关系型数据库中的一行记录,但它不需要事先定义结构。
集合
- 集合就是一组文档
- 文档类似关系库里的行,集合类似关系库里的表
- 集合中的文档无需固定的结构
数据库
- 多个文档组成集合,多个集合组成数据库
- 一个实例可以承载多个数据库(即一个 MongoDB 可以有多个数据库)
- 每个数据库都有独立的权限
admin、local、config是保留的数据库名称
安装与连接
安装可以直接使用发行版的包管理器或官方仓库提供的 mongodb 包,装好后用客户端连接:
1 | mongo # 连接本机数据库 |
⚠️ 注:官方客户端自 4.4 起逐步由
mongo迁移到mongosh,旧的mongoshell 已在 6.0 中移除。本文的语句在两者中基本通用,只有个别旧 API 有差异,遇到时会单独提示。
mongo shell 兼容 JavaScript
mongo shell 本身就是一个 JavaScript 解释器,可以直接做运算、定义函数,这也是后面能用变量承接查询结果的原因:
1 | > 1+2 |
2. 库、集合与文档的基本操作
查看与切换数据库
1 | > show dbs |
新增数据
常用的三个写入方法:db.collection.insert()、db.collection.insertOne()、db.collection.insertMany()。
1 | > stu = { name : 'weilai', age : 22 } |
批量写入用 insertMany(),传入一个文档数组,返回所有新文档的 _id:
1 | > use test |
这 20 条测试文档是用一小段 Python 脚本随机生成的,脚本见文末「批量生成测试数据」一节,下面的查询与更新示例都基于这批数据。
查询数据
db.collection.find() 返回全部匹配文档,db.collection.findOne() 只返回第一条:
1 | > db.students.find() |
find() 的第一个参数是查询条件,第二个参数是投影(字段为 1 表示返回,0 表示不返回):
1 | > db.students.find({sex:'male'},{age:1,name:1,_id:0}) // 只看 male,且只返回 name 与 age |
排序用 sort(),1 为升序、-1 为倒序:
1 | > db.students.find({},{_id:0}).sort({age:1}) // 不显示 _id,按 age 升序 |
修改数据
db.collection.update() 的第二个参数如果直接给一个文档,是整文档替换而不是只改其中一个字段:
1 | > db.students.update({name:'weilai'},{name:'cool'}) |
想保留其他字段,一种笨办法是先取出文档、改完再整体写回:
1 | > s_obj = db.students.findOne({'name':'weilai1'}) |
更常规的做法是用修改器:$set 设置字段值,$inc 让数值字段自增;默认只改第一条匹配的文档,加上 {multi:true} 才会改全部:
1 | > db.students.update({},{'$set':{class:'class 1'}}) // 仅修改第一条数据 |
回显里 nMatched 和 nModified 是两个数:前者是条件匹配到的条数,后者是真正被改动的条数。如果 $set 写进去的值和原值完全一样,这条文档只计入 nMatched 而不计入 nModified。上面第二条命令把第一个文档的 class 1 改成了 class 2、其余 19 个文档是新增字段,20 条都发生了实际变化,所以两个数相等;要是把值也写成 class 1,就会看到 nMatched: 20, nModified: 19。
删除数据
1 | > db.students.remove({name:'cool'}) // 删除匹配的数据 |
3. 常用命令速查
这一节按 SQL 的习惯把命令归了类,方便查阅。
数据库
1 | db / db.getName() // 查看当前数据库(db 就是 Database) |
⚠️ 注:
db.copyDatabase()与db.repairDatabase()已在 MongoDB 4.2 移除,新版本中请分别改用mongodump/mongorestore迁移数据、用mongod --repair修复数据文件。
集合
1 | db.createCollection("collName") // 创建普通集合,不带参数就够了 |
固定集合这三个参数容易写错:capped 是布尔值而不是数字;size 是这个集合的字节上限(上面 1048576 即 1MB,给太小的值会被抬到 mongod 的最小值),而且开 capped 时它是必填的;max 是文档条数上限,只在 capped: true 下才生效,两个上限哪个先到就先触发覆盖。
查询数据
1 | db.table.find() // select * from table; 数据太多时输入 it 回车翻页 |
比较与集合类操作符:$gt 大于、$lt 小于、$gte 大于等于、$lte 小于等于、$ne 不等于、$or 或、$in 包含、$nin 不包含。
索引
索引用于加快查找速度,主要是降低 CPU 成本消耗。索引与排序的区别在于:排序会重新排列原数据、改变原数据的排列顺序,而索引只建立一个顺序表,由这个顺序表指出数据的顺序,不改变原数据的排列顺序。
索引本身也分方向(建索引时字段写 1 或 -1),但单字段索引的方向其实无所谓——B 树可以正着扫也可以倒着扫,{name: 1} 这一个索引同时能满足 sort({name: 1}) 和 sort({name: -1})。方向真正起作用的是复合索引:只有索引的方向组合与 sort() 完全一致、或者完全相反(每个字段都取反),才能直接用索引出顺序而免掉内存里的排序。比如 {name: 1, ts: -1} 这个索引能支撑 sort({name: 1, ts: -1}) 和 sort({name: -1, ts: 1}),但支撑不了 sort({name: 1, ts: 1})。
MongoDB 支持的索引有:_id 索引(默认建立)、单键索引、多键索引、复合索引、过期索引、全文索引、地理位置索引。
1 | db.table.ensureIndex({name: 1}) // 创建索引 |
⚠️ 注:
ensureIndex()自 3.0 起已是createIndex()的别名,并在新版 shell 中被移除,新代码请直接使用createIndex()。
添加数据与导入
1 | db.table.insert({}) |
导入整个数据文件用 mongoimport:
1 | mongoimport -d demo -c product --file D:\MongoDB\data\product.json --jsonArray |
修改数据
1 | // update table set number = '111' where name = 'baiqi'; 第一个 false 表示不新增数据,最后的 true 表示批量 |
删除数据
1 | db.table.remove({_id: ObjectId("59a67e103cbae75d282fe46b")}) |
其他
1 | db.getPrevError() // 查询之前的错误信息 |
⚠️ 注:
insert()、update()、remove()属于早期 API,在新版本的 mongosh 与各语言驱动中已标记废弃,建议改用insertOne()/insertMany()、updateOne()/updateMany()、deleteOne()/deleteMany()。
4. 使用 pymongo 操作 MongoDB
Python 侧用官方驱动 pymongo,方法名与 shell 中的新版 API 一一对应(insert_one、find_one、update_many……),下面把增删改查封装成一个类便于对照:
1 | from pymongo import MongoClient |
几个返回值的用法值得留意:插入单条后用 inserted_id 拿到新文档的 _id,批量插入用 inserted_ids;更新看 matched_count,删除看 deleted_count;按 _id 查询时字符串要先用 ObjectId() 包一层。
5. 批量生成测试数据
第 2 节 insertMany() 用到的 20 条文档就是这段脚本输出的:用 random 随机生成字段,再用 json 拼成 [{},{}...] 形式的数组,可以直接粘进 insertMany(),也可以存成文件交给 mongoimport --jsonArray 导入。
1 | import random |