传统的关系型数据库(如MySQL),在数据操作的“三高”需求以及应对Web2.0的网站需求面前,显得力不从心。
解释:“三高”需求:
而MongoDB可应对“三高”需求,具体的应用场景如:
这些应用场景中,数据操作方面的共同特点是:
对于这样的数据,更适合使用MongoDB来实现数据的存储。
如果上述有1个符合,可以考虑 MongoDB,2个及以上的符合,选择 MongoDB 绝不会后悔。
MySQL和MongoDB对比:
MongoDB的最小存储单位就是文档(document)对象。文档(document)对象对应于关系型数据库的行。数据在MongoDB中以BSON(Binary-JSON)文档的格式存储在磁盘上。
BSON(Binary Serialized Document Format)是一种类json的一种二进制形式的存储格式,简称Binary JSON。BSON和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型。
BSON采用了类似于 C 语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是空间利用率不是很理想。
Bson中,除了基本的JSON类型:string,integer,boolean,double,null,array和object,mongo还使用了特殊的数据类型。这些类型包括date,object id,binary data,regular expression 和code。每一个驱动都以特定语言的方式实现了这些类型,查看你的驱动的文档来获取详细信息。
BSON数据类型参考列表:
shell默认使用64位浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用NumberInt(4字节符号整数)或NumberLong(8字节符号整数),{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}
MongoDB主要有如下特点:
高性能:
MongoDB提供高性能的数据持久性。特别是,对嵌入式数据模型的支持减少了数据库系统上的I/O活动。
索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。(文本索引解决搜索的需求、TTL索引解决历史数据自动过期的需求、地理位置索引可用于构建各种 O2O 应用)
mmapv1、wiredtiger、mongorocks(rocksdb)、in-memory 等多引擎支持满足各种场景需求。
Gridfs解决文件存储的需求。
高可用性:
高扩展性:
丰富的查询支持:
其他特点:如无模式(动态模式)、灵活的文档模型。
基本配置:
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# Where and how to store data.
storage:
dbPath: E:\mongodb\data
journal:
enabled: true
# engine:
# wiredTiger:
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: E:\mongodb\log\mongod.log
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1
启动MongoDB服务:
mongod -f /mongodb/single/mongod.conf
如果一旦是因为数据损坏,则需要进行如下操作(了解):
删除lock文件:
rm -f /mongodb/single/data/db/*.lock
修复数据:
/usr/local/mongdb/bin/mongod --repair --dbpath=/mongodb/single/data/db
存放文章评论的数据存放到MongoDB中,数据结构参考如下,数据库:articledb
选择和创建数据库的语法格式,如果数据库不存在则自动创建,例如,以下语句创建 spitdb 数据库:
use articledb
一般使用操作conllection的方式隐式创建数据库,这样的操作既会创建数据库又会创建collections
use articledb
db.articlecollection.insertOne({"id":1,"name":"article"})
show dbs
或
show databases
在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。
db
MongoDB 中默认的数据库为 test,如果你没有选择数据库,集合将存放在 test 数据库中。
数据库名可以是满足以下条件的任意UTF-8字符串:
有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
5. admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
6. local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
7. config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
db.dropDatabase()
集合的命名规范:
show collections
或
show tables
当向一个集合中插入一个文档的时候,如果集合不存在,则会自动创建集合。
db.articlecollection.insertOne({"id":1,"name":"article"})
db.collection.drop()
或
db.集合.drop()
如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。
db.oldcollectionName.renameCollection(“newName”)
原理就是从当前表中查询出来,循环写入到另一份表中:
db.collectionName.find().forEach(function(x){db.collection.baktableName.insert(x)})
文档(document)的数据结构和 JSON 基本一样,所有存储在集合中的数据都是 BSON 格式。
使用insertOne() 或 insert() (不推荐)或save() (弃用)方法向集合中插入文档,语法如下:
db.collection.insertOne(
<document or array of documents>,
{
writeConcern: <document>,
ordered: <boolean>
}
)
db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明
媚","userid":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null})
提示:
1)comment集合如果不存在,则会隐式创建
2)mongo中的数字,默认情况下是double类型,如果要存整型,必须使用函数NumberInt(整型数字),否则取出来就有问题了。
3)插入当前日期使用 new Date()
4)插入的数据没有指定 _id ,会自动生成主键值
5)如果某字段没值,可以赋值为null,或不写该字段。
注意:
文档键命名规范:
语法:
db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)
批量插入多条文章评论:
db.comment.insertMany([
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我
他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-
05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},
{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔
悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},
{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船
长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},
{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯
撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},
{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫
嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-
06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);
插入时指定了 _id ,则主键就是该值,如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚掉。
查询数据的语法格式如下:
db.collection.find(<query>, [projection])
db.comment.find()
或
db.comment.find({})
简单条件来查询:
db.comment.find({userid:'1003'})
如果你只需要返回符合条件的第一条数据,可以使用findOne命令来实现,语法和find一样:
db.comment.findOne({userid:'1003'})
投影查询(Projection Query):
# 默认 _id 会显示,可以设置_id为0来取消
db.comment.find({userid:"1003"},{userid:1,nickname:1,_id:0})
{ "userid" : "1003", "nickname" : "凯撒" }
{ "userid" : "1003", "nickname" : "凯撒" }
嵌套查询:
{
"_id":ObjectId("11223344"),
"account":{
"username":"123",
"password":{
"truth":"123"
"hash":"1hdhh22"
}
}
}
db.userinfo.find({"account.password.truth":"123"})
更新文档的语法:
db.collection.update(query, update, options)
//或
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2
}
)
// 或
updateOne / updateMany
覆盖的修改:
如果想修改_id为1的记录,点赞量为1001,输入以下语句:
db.comment.update({_id:"1"},{likenum:NumberInt(1001)})
执行后会发现,这条文档除了likenum字段其它字段都不见了
局部修改:
为了解决这个问题,需要使用修改器$set
来实现,命令如下:
db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})
批量的修改
更新所有用户为 1003 的用户的昵称为 凯撒大帝 。
//默认只修改第一条数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒2"}})
//修改所有符合条件的数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})
提示:如果不加后面的参数,则只更新符合条件的第一条记录
db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})
删除文档的语法结构:
db.集合名称.remove(条件)
# 全部删除
db.comment.remove({})
db.comment.count({userid:"1003"})
# 或
db.comment.find({userid:"1003"}).count()
默认情况下 count() 方法返回符合条件的全部记录条数。
高版本的mongoApi废弃了count()方法,如果需要统计数量可以使用:
python的collection.estimated_document_count(),collection.count_documents(filter)
go的collection.CountDocuments(ctx,filter),collection.EstimatedDocumentCount(ctx)
collection.estimated_document_count()
和collection.count_documents(filter)
都是MongoDB的方法,用于获取集合中符合条件的文档数量。然而,它们在计算文档数量时存在一些区别。
collection.estimated_document_count()
: 这个方法返回的是集合中所有文档的估计数量。它是一个快速的近似计数方法,不会真正遍历集合中的每个文档。这个方法通常用于获取一个大致的文档数量,而不需要非常精确的结果。它比较适用于大型集合,因为它不需要扫描所有文档来计算数量,而是使用一些统计信息或近似算法进行估算。
collection.count_documents(filter)
: 这个方法返回符合指定条件的文档数量。你需要传递一个查询条件(filter)给这个方法,它会扫描集合中的每个文档,并统计满足条件的文档数量。这个方法提供了精确的文档数量,因为它会确切地计算满足条件的文档数目。但是,它可能会比较慢,特别是对于大型集合或复杂的查询条件。
所以,collection.estimated_document_count()
提供了一个快速的估计值,而collection.count_documents(filter)
提供了精确的满足条件的文档数量。你可以根据你的需求选择使用哪个方法。如果你需要一个快速的近似值,可以使用estimated_document_count()
;如果你需要准确的数量,可以使用count_documents(filter)
。
可以使用limit()方法来读取指定数量的数据,使用skip()方法来跳过指定数量的数据。
>db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)
如果你想返回指定条数的记录,可以在find方法后调用limit来返回结果(TopN),默认值20,例如:
db.comment.find().limit(3)
skip方法同样接受一个数字参数作为跳过的记录条数。(前N个不要),默认值是0
db.comment.find().skip(3)
分页查询:需求:每页2个,第二页开始:跳过前两条数据,接着值显示3和4条数据
//第一页
db.comment.find().skip(0).limit(2)
//第二页
db.comment.find().skip(2).limit(2)
//第三页
db.comment.find().skip(4).limit(2)
sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。
skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit(),和命令编写顺序无关。
db.comment.find().sort({userid:-1,likenum:1})
MongoDB的模糊查询是通过正则表达式的方式实现的。格式为:
db.collection.find({field:/正则表达式/})
或
db.集合.find({字段:/正则表达式/}
# 如果要查询评论的内容中以“专家”开头的,代码如下:
db.comment.find({content:/^专家/})
正则还有一种表达方式,与//
是具有同样的功能
$regex:正则 db.products.find( { sku: { $regex: /^ABC/i } }
二者区别在于,当需要$options时:
使用//表示正则表达式时,可以在正斜杠后面添加一个或多个选项字符来设置匹配选项。常见的选项字符包括:
i:表示不区分大小写。
m:表示多行模式,使^和$匹配每一行的开头和结尾。
s:表示单行模式,使.可以匹配包括换行符在内的任意字符。
x:表示忽略正则表达式中的空白字符和注释。
db.collection.find({ field: /pattern/i })
db.collection.find({ field: { $regex: "pattern", $options: "i" } })
db.集合名称.find({ "field" : { $gt: value }}) // 大于: field > value
db.集合名称.find({ "field" : { $lt: value }}) // 小于: field < value
db.集合名称.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.集合名称.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.集合名称.find({ "field" : { $ne: value }}) // 不等于: field != value
# 查询age=20的文档:
db.person.find( { age: { $eq: 20 } } )相当于:db.person.find( { age: 20 } )
包含使用$in操作符。 示例:查询评论的集合中userid字段包含1003或1004的文档
db.comment.find({userid:{$in:["1003","1004"]}})
不包含使用$nin操作符。 示例:查询评论集合中userid字段不包含1003和1004的文档
db.comment.find({userid:{$nin:["1003","1004"]}})
如果需要查询同时满足两个以上条件,需要使用$and操作符将条件进行关联。(相 当于SQL的and) 格式为:
$and:[ { },{ },{ } ]
示例:查询评论集合中likenum大于等于700 并且小于2000的文档:
db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})
如果两个以上条件之间是或者的关系,我们使用or操作符进行关联,与前面 and的使用方式相同格式为:
$or:[ { },{ },{ } ]
示例:查询评论集合中userid为1003,或者点赞数小于1000的文档记录
db.comment.find({$or:[ {userid:"1003"} ,{likenum:{$lt:1000} }]})
$nor:一个条件都不满足,查询age既不等于20,sex也不是男的文档:
db.person.find( {$nor: [ { age: 20 },{ sex: "男"} ] } )
distinct是一个用于获取唯一值列表的聚合操作符。它用于查找集合中某个字段的所有不重复的值,并返回一个包含这些唯一值的数组。
# 语法
db.collection.distinct(field, query)
db.collectionName.distinct(‘key’)
假设我们有一个名为"users"的集合,包含了一些用户文档,每个文档都有一个"country"字段表示用户所在的国家。我们可以使用distinct操作来获取所有不重复的国家列表:
db.users.distinct("country")
这将返回一个数组,包含集合中所有不重复的"country"值。
如果需要格式化查询的结果,可以使用pretty()的方法:
可参考菜鸟文档:菜鸟文档
命令 | 功能描述 |
$project | 指定输出文档里的字段. |
$match | 选择要处理的文档,与fine()类似。 |
$limit | 限制传递给下一步的文档数量。 |
$skip | 跳过一定数量的文档。 |
$unwind | 扩展数组,为每个数组入口生成一个输出文档。(把列表拆开) |
$group | 根据key来分组文档。 |
$sort | 排序文档。 |
$geoNear | 选择某个地理位置附近的的文档。 |
$out | 把管道的结果写入某个集合。 |
$redact | 控制特定数据的访问。 |
$lookup | 多表关联(3.2版本新增) |
相当关系型数据库中多表关联查询
属性 | 作用 |
---|---|
from | 同一个数据库下等待被Join的集合。 |
localField | 源集合中的match值,如果输入的集合中,某文档没有 localField这个Key(Field),在处理的过程中,会默认为此文档含有 localField:null的键值对。 |
foreignField | 待Join的集合的match值,如果待Join的集合中,文档没有foreignField值,在处理的过程中,会默认为此文档含有 foreignField:null的键值对。 |
as | 为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉 |
// 管道是一个切片,里面可以放很多操作
db.collection.aggregate([{
$lookup: {
from: "外键表",
localField: "本表外键字段",
foreignField: "外键表字段",
as: "起别名"
}
})
unc main() {
VulnInfo:=GetCollection("SVD_DATA","VulnInfo")
cur,err:=VulnInfo.Aggregate(context.Background(),[]bson.M{
// 外键查询
{"$lookup":bson.M{
"from":"VulnTags",
"localField":"tags",
"foreignField":"id",
"as":"vuln_tags",
}},
// 限制两个
{
"$limit":2,
},
// 不要_id
{
"$project":bson.M{
"_id":false,
},
},
},)
if err!=nil{
fmt.Println(err)
}else{
dic:=make(map[string]interface{})
for cur.Next(context.TODO()){
err:=cur.Decode(dic)
if err!=nil{
fmt.Println(err)
}else{
fmt.Println(dic)
}
}
}
}
$
$
在 update 中 加上关键字 就变成了修改器,其实 $
字符 独立出现也是有意义的 , 可以理解为一个代指符。
现在把 “score”: 100 的 test_list 里面的 2 改为 9,{$set :{"test_list.0" : 9}}
这样就是对应 Array 中的下标进行修改了 “test_list.下标”,如果是一个很长很长很长的 Array 怎么整呢?
$ 字符在语句中代表了下标,位置,使用 update的话, 满足条件的数据下标位置就会传递到 $ 字符中。
$exists是一个查询操作符,用于检查字段是否存在于文档中。它可以用于查找具有特定字段的文档或者查找没有特定字段的文档。
语法:
{ field: { $exists: <boolean> } }
用法:
存在db.person.find( { phone: { $exists: true } } )
注意:
$exists
操作符并不关心字段的实际值,只关心字段是否存在。如果指定的字段存在于文档中,不论其值是什么,都会被匹配到。
可以将$exists操作符与其他查询操作符组合使用,以构建更复杂的查询条件。
对于在嵌套文档或数组中的字段,$exists
操作符只会检查字段是否存在于文档中,而不会检查字段的值是否为空或非空。如果需要同时检查字段是否存在和其值是否为空,可以结合使用$exists
和其他查询操作符,如$eq
、$ne
等。
$mod是一个查询操作符,用于匹配字段值与指定除法操作的余数条件,
语法:
{ field: { $mod: [ divisor, remainder ] } }
field是要匹配的字段名,divisor是除数,remainder是余数。
# 查询age字段的值除以2余0的文档
余数db.person.find( { age: { $mod: [ 2, 0 ] } } )
$mod
操作符只能用于整数字段,并且它只能匹配满足给定除法操作的整数余数条件的文档。
$unset是MongoDB的更新操作符之一,用于从文档中移除指定的字段。
语法:
{ $unset: { field1: "", field2: "", ... } }
field1、field2等是要移除的字段名,空字符串 “” 用于表示移除该字段。
# 删除某个字段
db.person.update( { _id: 1}, { $unset: { name:"" } })
$unset操作符只能用于更新操作(如updateOne、updateMany等),它不适用于查询操作。
如果指定的字段不存在于文档中,$unset操作符不会引发错误,它会忽略该字段。
$type
是MongoDB的查询操作符之一,用于匹配指定字段的数据类型。
$type
的使用方式如下:
{ field: { $type: <type> } }
在上述示例中,field是要匹配的字段名,
是一个数值或字符串,用于指定要匹配的数据类型。
# 使用$type操作符来查找"price"字段类型为double的文档:
db.products.find({ price: { $type: "double" } })
MongoDB支持的数据类型包括:
$size
是MongoDB的查询操作符之一,用于匹配数组字段的大小(元素数量)。
{ field: { $size: <size> } }
field
是要匹配的数组字段名,
是一个整数,用于指定要匹配的数组大小。
使用$size
操作符来查找"items"数组大小为3的文档:
db.orders.find({ items: { $size: 3 } })
$size操作符只能用于匹配数组字段的大小,不适用于其他数据类型。
另外,对于嵌套数组,$size操作符会匹配整个数组的大小,而不仅限于顶层数组。
$mul
是MongoDB的更新操作符之一,用于将指定字段的值乘以给定的因子。
{ $mul: { field: <factor> } }
field
是要更新的字段名,
是一个数字,表示要乘以的因子。
可以使用$mul操作符将"price"字段的值乘以2:
db.inventory.updateMany({}, { $mul: { price: 2 } })
$mul
操作符只能用于更新操作(如updateOne、updateMany等),它不适用于查询操作。
$rename
是MongoDB的更新操作符之一,用于重命名文档中的字段。
{ $rename: { oldField: newField } }
oldField
是要重命名的字段名,newField
是字段的新名称。
可以使用$rename
操作符将"name"字段重命名为"fullName":
db.users.updateMany({}, { $rename: { name: "fullName" } })
$rename
操作符只能用于更新操作(如updateOne、updateMany等),它不适用于查询操作。
另外,如果指定的旧字段不存在于文档中,$rename操作符不会引发错误,它会忽略该字段。
$elemMatch
是MongoDB的查询操作符之一,用于在数组字段中匹配满足多个查询条件的元素。
{ field: { $elemMatch: { condition1, condition2, ... } } }
field
是要匹配的数组字段名,condition1、condition2等是一个或多个查询条件,用于指定要匹配的元素条件。
假设有一个名为"orders"的集合,其中的文档包含了"items"字段,它是一个数组,每个元素都是一个嵌套文档,包含了"product"和"quantity"字段。可以使用$elemMatch操作符来查找满足多个条件的"items"元素:
db.orders.find({ items: { $elemMatch: { product: "Apple", quantity: { $gte: 10 } } } })
上述示例将返回所有满足条件的文档,其中"items"数组至少包含一个元素,该元素的"product"字段为"Apple"且"quantity"字段大于等于10。
需要注意的是,$elemMatch
操作符可以在数组字段中匹配多个条件,并且它将仅返回满足所有条件的文档。如果不使用$elemMatch,数组字段中的每个条件将作为独立的匹配条件处理。
$push
是MongoDB的更新操作符之一,用于向数组字段中添加一个或多个元素。
{ $push: { field: <value> } }
field
是要更新的数组字段名,
是要添加到数组中的元素。
假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$push操作符将新的喜爱项添加到"favorites"数组中:
db.users.updateOne({ _id: ObjectId("...") }, { $push: { favorites: "Book" } })
如果指定的数组字段不存在,$push操作符将会自动创建该数组字段,并将元素添加到其中。
另外,$push
操作符还可以添加多个元素到数组中,可以使用$each
修饰符和数组来指定要添加的多个元素。
不去重
$pull
是MongoDB的更新操作符之一,用于从数组字段中移除满足指定条件的元素。
{ $pull: { field: <condition> } }
field
是要更新的数组字段名,
是一个查询条件,用于指定要移除的元素条件。
假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$pull操作符移除"favorites"数组中的特定元素:
db.users.updateOne({ _id: ObjectId("...") }, { $pull: { favorites: "Book" } })
$pull
操作符可以移除满足指定条件的所有元素,而不仅仅是第一个匹配的元素。
$pop
是MongoDB的更新操作符之一,用于从数组字段中移除第一个或最后一个元素。
{ $pop: { field: <value> } }
field
是要更新的数组字段名,
是一个数值,用于指定要移除的元素位置。若
为 1,则移除最后一个元素;若
为 -1,则移除第一个元素。
假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$pop操作符从"favorites"数组中移除第一个或最后一个元素:
db.users.updateOne({ _id: ObjectId("...") }, { $pop: { favorites: 1 } })
使用$pop操作符时,若数组字段不存在或为空,不会引发错误,而是忽略操作。
另外,可以在同一个更新操作中多次使用$pop操作符来移除多个元素。
$all
是MongoDB的查询操作符之一,用于匹配包含多个元素的数组字段。
{ field: { $all: [<value1>, <value2>, ...] } }
field
是要匹配的数组字段名,
是要匹配的多个元素。
假设有一个名为"products"的集合,其中的文档包含了"tags"字段,它是一个数组,用于标记产品的标签。可以使用$all操作符来查找包含指定标签的产品:
db.products.find({ tags: { $all: ["electronics", "smartphone"] } })
$addToSet
是MongoDB的更新操作符之一,用于向数组字段中添加一个元素,但仅在该元素不存在于数组中时才添加。
{ $addToSet: { field: <value> } }
field
是要更新的数组字段名,
是要添加到数组中的元素。
假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$addToSet操作符将新的喜爱项添加到"favorites"数组中,但只有当该元素在数组中不存在时才会添加:
db.users.updateOne({ _id: ObjectId("...") }, { $addToSet: { favorites: "Book" } })
如果指定的数组字段不存在,$addToSet操作符将会自动创建该数组字段,并将元素添加到其中。
另外,$addToSet
操作符还可以添加多个元素到数组中,可以使用$each
修饰符和数组来指定要添加的多个元素。
对于"_id" : ObjectId("5b151f8536409809ab2e6b26")
,其组成如下:
MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)。
对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。
MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)。
复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后在每个userid的值内,再在按score倒序排序。
db.collection.getIndexes()
查看comment集合中所有的索引情况:
> db.comment.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "articledb.comment"
}
]
结果中显示的是默认 _id
索引:
_id
字段上创建一个唯一的索引,默认名字为 _id
,该索引可防止客户端插入两个具有相同值的文档,您不能在_id
字段上删除此索引。_id
值不能重复的。在分片集群中,通常使用 _id
作为片键。db.collection.createIndex(keys, options)
参数:
options(更多选项)列表:
单字段索引示例:对 userid 字段建立索引:
> db.comment.createIndex({userid:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
复合索引:对 userid 和 nickname 同时建立复合(Compound)索引:
> db.comment.createIndex({userid:1,nickname:-1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
db.collection.dropIndex(index)
参数:
删除 comment 集合中 userid 字段上的升序索引:
> db.comment.dropIndex({userid:1})
{ "nIndexesWas" : 3, "ok" : 1 }
所有索引的移除:
db.collection.dropIndexes()
提示: _id
的字段的索引是无法删除的,只能删除非 _id
字段的索引。
分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是否基于索引查询等。
那么,通常想知道,建立的索引是否有效,效果如何,都需要通过执行计划查看。
db.collection.find(query,options).explain(options)
查看根据userid查询数据的情况:
> db.comment.find({userid:"1003"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "articledb.comment",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1003"
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"userid" : {
"$eq" : "1003"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "9ef3740277ad",
"port" : 27017,
"version" : "4.0.10",
"gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
},
"ok" : 1
}
关键点看: “stage” : “COLLSCAN”, 表示全集合扫描,: “stage” : “IXSCAN” ,基于索引的扫描
当查询条件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。 这些覆盖的查询可以非常有效。
mongoexport -h ip:port -u 用户名 -p 密码-d 数据库名 -c 集合名 -o 输出的文件路径以及文件名 --type json/csv -f 字段名
mongoimport -d "指定要被导入数据的数据库名" -c "指定表名" --file "指定被导入的文件名" --headline --type "指定类型" -f "指定字段"
mongodump -h "数据库所在ip地址" -d "数据库名称" -o "保存文件的目录"
mongorestore -h "数据库所在ip" -d "要保存数据的数据库名称" --dir "存放数据的目录"
mongoexport
导出json文件mongoimport
导入文件时 卡到某个进度不再动读取文件再利用pymongo写入操作,但此方法容易有编码,bson格式,时间格式等错误
这个参数设置worker的数量,可以理解为并发数,设置--numInsertionWorkers 100
或者更多,数据就可以完全导入,但最后还是会卡在100%
如果只加workers数量不行的话,可以加--batchSize 10000
步长试试。
GridFS 用于存储和恢复那些超过16M(BSON文件限制)的文件(如:图片、音频、视频等)。
GridFS 也是文件存储的一种方式,但是它是存储在MonoDB的集合中。
GridFS 可以更好的存储大于16M的文件。
GridFS 会将大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。
GridFS 用两个集合来存储一个文件:fs.files与fs.chunks。
每个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filename,content_type,还有用户自定义的属性)将会被存在files集合中。
调用 MongoDB 安装目录下bin的 mongofiles.exe工具
存储文件:mongofiles –d database -l filename put new_filename
以这种命令上传文件,文件名为new_filename,不会携带路径
from pymongo import MongoClient
from gridfs import GridFS
client = MongoClient()
database = client.get_database('files')
gf = GridFS(database=database)
res = gf.find_one()
print(res)
{'_GridOut__buffer': b'',
'_GridOut__chunk_iter': None,
'_GridOut__chunks': Collection(Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'files'), 'fs.chunks'),
'_GridOut__file_id': None,
'_GridOut__files': Collection(Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'files'), 'fs.files'),
'_GridOut__position': 0,
'_file': {'_id': ObjectId('6236e52955dd69298cc54a16'),
'chunkSize': 261120,
'filename': 'test.txt',
'length': 18,
'md5': '7552e7c2987420e42fa1b19a0988c7a1',
'uploadDate': datetime.datetime(2022, 3, 20, 8, 26, 17, 943000)},
'_session': None}
# 打印文件列表['filename1','filename2',...]
print(gf.list())
# 下载文件
with open('./gettest.txt','wb') as f:
for line in gf.get(res._id):
f.write(line)
# 上传文件
with open('./gettest.txt','rb') as f:
gf.put(f.read(),filename='gettest.txt')
事务(GO)
要获取事务支持则需要安装对应的或高版本的mongodb
和驱动(pymongo(python)
,mongo-driver(go)
)
func (s *sessionImpl) WithTransaction(ctx context.Context, fn func(sessCtx SessionContext) (interface{}, error),
opts ...*options.TransactionOptions) (interface{}, error) {
// 超时时间为:120 * time.Second
timeout := time.NewTimer(withTransactionTimeout)
defer timeout.Stop()
var err error
for {
// 开启事务
err = s.StartTransaction(opts...)
if err != nil {
return nil, err
}
// 回调函数(会话上下文)
res, err := fn(NewSessionContext(ctx, s))
// 执行失败终止事务
if err != nil {
if s.clientSession.TransactionRunning() {
// 终止事务
_ = s.AbortTransaction(internal.NewBackgroundContext(ctx))
}
select {
case <-timeout.C:
return nil, err
default:
}
if errorHasLabel(err, driver.TransientTransactionError) {
continue
}
return res, err
}
// 判断在回调函数里面是否直接通过会话上下文终止事务了
err = s.clientSession.CheckAbortTransaction()
if err != nil {
return res, nil
}
if ctx.Err() != nil {
_ = s.AbortTransaction(internal.NewBackgroundContext(ctx))
return nil, ctx.Err()
}
// CommitLoop提交事务循环,还在上面那个for里面
CommitLoop:
for {
// 提交
err = s.CommitTransaction(ctx)
if err == nil {
// 返回成功结果 res, err := fn(NewSessionContext(ctx, s))
return res, nil
}
// 超时判断
select {
case <-timeout.C:
return res, err
default:
}
if cerr, ok := err.(CommandError); ok {
// UnknownTransactionCommitResult = "UnknownTransactionCommitResult"
if cerr.HasErrorLabel(driver.UnknownTransactionCommitResult) && !cerr.IsMaxTimeMSExpiredError() {
continue
}
// errorHasLabel:包含规定的错误信息,返回true
// TransientTransactionError = "TransientTransactionError"
if cerr.HasErrorLabel(driver.TransientTransactionError) {
break CommitLoop
}
}
return res, err
}
}
}
def with_transaction(
self,
callback: Callable[["ClientSession"], _T],
read_concern: Optional[ReadConcern] = None,
write_concern: Optional[WriteConcern] = None,
read_preference: Optional[_ServerMode] = None,
max_commit_time_ms: Optional[int] = None,
) -> _T:
start_time = time.monotonic()
while True:
self.start_transaction(read_concern, write_concern, read_preference, max_commit_time_ms)
try:
ret = callback(self)
except Exception as exc:
if self.in_transaction:
self.abort_transaction()
if (
isinstance(exc, PyMongoError)
and exc.has_error_label("TransientTransactionError")
and _within_time_limit(start_time)
):
# Retry the entire transaction.
continue
raise
if not self.in_transaction:
# Assume callback intentionally ended the transaction.
return ret
while True:
try:
self.commit_transaction()
except PyMongoError as exc:
if (
exc.has_error_label("UnknownTransactionCommitResult")
and _within_time_limit(start_time)
and not _max_time_expired_error(exc)
):
# Retry the commit.
continue
if exc.has_error_label("TransientTransactionError") and _within_time_limit(
start_time
):
# Retry the entire transaction.
break
raise
# Commit succeeded.
return ret
使用更加简便的with方法
func main() {
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.Connect(context.TODO(), clientOptions)
collection:=client.Database().Collection()
if err != nil {
log.Fatal(err)
}
wc := writeconcern.New(writeconcern.WMajority())
txnOptions := options.Transaction().SetWriteConcern(wc)
session,_:=client.StartSession()
result,_:=session.WithTransaction(context.TODO(), func(sessCtx mongo.SessionContext) (interface{}, error) {
result,err:=collection.Find(context.TODO(),bson.M{})
return result,err
},txnOptions)
cur,ok:=result.(*mongo.Cursor)
if ok{
fmt.Println("断言成功")
for cur.Next(context.TODO()){
m:=make(map[string]interface{})
cur.Decode(m)
fmt.Println(m)
}
}else{
fmt.Println("断言失败")
}
}
import pymongo
client = pymongo.MongoClient()
collection = client.get_database().get_collection()
def call_back(ctx: pymongo.ContextManager):
print(collection.find_one())
with client.start_session() as session:
session.with_transaction(call_back)