mongodb基础

目录

  • MongoDB相关概念
    • 什么时候选择MongoDB
  • MongoDB简介
    • 体系结构
    • 数据模型
    • MongoDB的特点
  • Mongodb配置
  • 基本常用命令
    • 数据库操作
      • 选择和创建数据库
      • 查看有权限查看的所有的数据库命令
      • 查看当前正在使用的数据库命令
      • 数据库的删除(删除当前数据库)
    • 集合操作
      • 查看当前库中的表:show tables命令
      • 集合的隐式创建
      • 集合的删除
      • 集合重命名
      • 备份表
    • 文档CRUD
      • 文档的插入
        • 单个文档插入
        • 批量插入
      • 文档的基本查询
      • 文档的更新
      • 删除文档
      • 高级查询
        • 统计查询
        • 分页列表查询
        • 排序查询
        • 正则的复杂条件查询
        • 比较查询
        • 包含查询
        • 条件连接查询
        • 去重
        • 格式化输出
        • 聚合查询
          • 管道
          • 聚合管道操作
          • $lookup
          • 实操
        • 神奇的$运算符
          • `$`
          • $exists
          • $mod
          • $unset
          • $type
          • $size
          • $mul
          • $rename
          • $elemMatch
          • $push
          • $pull
          • $pop
          • $all
          • $addToSet
  • ObjectId
  • 索引-Index
    • 索引的类型
      • 单字段索引
      • 复合索引
      • 其他索引
    • 索引的管理操作
      • 索引的查看
      • 索引的创建
      • 索引的移除
      • 索引的使用
        • 执行计划
        • 涵盖的查询
  • mongo导出导入
    • 导出mongoexport
    • 导入mongoimport
    • 备份mongodump
    • 恢复mongorestore
    • 认证
    • mongoimport导入json数据阻塞,导入不完全
      • 解决方法:
        • 方法一:pymongo
        • 方法二:numInsertionWorkers
  • GridFS
    • MongoDB GridFS
    • 使用
    • 具体操作
    • pymongo操作
  • 原子操作&事务
    • 原子操作
    • 事务
    • 官方文档:
    • 支持版本
    • 内置的一些原子操作
    • 事务支持
      • go
      • python
    • 跑通代码
      • go
      • python

MongoDB相关概念

传统的关系型数据库(如MySQL),在数据操作的“三高”需求以及应对Web2.0的网站需求面前,显得力不从心。
解释:“三高”需求:

  • High performance - 对数据库高并发读写的需求。
  • Huge Storage - 对海量数据的高效率存储和访问的需求。
  • High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求。

而MongoDB可应对“三高”需求,具体的应用场景如:

  1. 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
  2. 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。
  3. 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将
    订单所有的变更读取出来。
  4. 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
  5. 视频直播,使用 MongoDB 存储用户信息、点赞互动信息等。

这些应用场景中,数据操作方面的共同特点是:

  1. 数据量大
  2. 写入操作频繁(读写都很频繁)
  3. 价值较低的数据,对事务性要求不高

对于这样的数据,更适合使用MongoDB来实现数据的存储。

什么时候选择MongoDB

  1. 应用不需要事务及复杂 join 支持
  2. 新应用,需求会变,数据模型无法确定,想快速迭代开发
  3. 应用需要2000-3000以上的读写QPS(更高也可以)
  4. 应用需要TB甚至 PB 级别数据存储
  5. 应用发展迅速,需要能快速水平扩展
  6. 应用要求存储的数据不丢失
  7. 应用需要99.999%高可用
  8. 应用需要大量的地理位置查询、文本查询

如果上述有1个符合,可以考虑 MongoDB,2个及以上的符合,选择 MongoDB 绝不会后悔。

MongoDB简介

  1. MongoDB是一个开源、高性能、无模式的文档型数据库,当初的设计就是用于简化开发和方便扩展,是NoSQL数据库产品中的一种。是最像关系型数据库(MySQL)的非关系型数据库。
  2. 它支持的数据结构非常松散,是一种类似于 JSON 的 格式叫BSON,所以它既可以存储比较复杂的数据类型,又相当的灵活。
  3. MongoDB中的记录是一个文档,它是一个由字段和值对(field:value)组成的数据结构。MongoDB文档类似于JSON对象,即一个文档认为就是一个对象。字段的数据类型是字符型,它的值除了使用基本的一些类型外,还可以包括其他文档、普通数组和文档数组。

体系结构

MySQL和MongoDB对比:

mongodb基础_第1张图片
mongodb基础_第2张图片

数据模型

  1. MongoDB的最小存储单位就是文档(document)对象。文档(document)对象对应于关系型数据库的行。数据在MongoDB中以BSON(Binary-JSON)文档的格式存储在磁盘上。

  2. BSON(Binary Serialized Document Format)是一种类json的一种二进制形式的存储格式,简称Binary JSON。BSON和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型。

  3. BSON采用了类似于 C 语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是空间利用率不是很理想。

  4. Bson中,除了基本的JSON类型:string,integer,boolean,double,null,array和object,mongo还使用了特殊的数据类型。这些类型包括date,object id,binary data,regular expression 和code。每一个驱动都以特定语言的方式实现了这些类型,查看你的驱动的文档来获取详细信息。

BSON数据类型参考列表:
mongodb基础_第3张图片
shell默认使用64位浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用NumberInt(4字节符号整数)或NumberLong(8字节符号整数),{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}

MongoDB的特点

MongoDB主要有如下特点:

  1. 高性能:

    • MongoDB提供高性能的数据持久性。特别是,对嵌入式数据模型的支持减少了数据库系统上的I/O活动。

    • 索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。(文本索引解决搜索的需求、TTL索引解决历史数据自动过期的需求、地理位置索引可用于构建各种 O2O 应用)

    • mmapv1、wiredtiger、mongorocks(rocksdb)、in-memory 等多引擎支持满足各种场景需求。

    • Gridfs解决文件存储的需求。

  2. 高可用性:

    • MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。
  3. 高扩展性:

    • MongoDB提供了水平可扩展性作为其核心功能的一部分。
    • 分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展)
    • 从3.4开始,MongoDB支持基于片键创建数据区域。在一个平衡的集群中,MongoDB将一个区域所覆盖的读写只定向到该区域内的那些片。
  4. 丰富的查询支持:

    • MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等。
  5. 其他特点:如无模式(动态模式)、灵活的文档模型。

Mongodb配置

基本配置:

# 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

如果一旦是因为数据损坏,则需要进行如下操作(了解):

  1. 删除lock文件:

    rm -f /mongodb/single/data/db/*.lock
    
  2. 修复数据:

    /usr/local/mongdb/bin/mongod --repair --dbpath=/mongodb/single/data/db
    

基本常用命令

存放文章评论的数据存放到MongoDB中,数据结构参考如下,数据库:articledb
mongodb基础_第4张图片

数据库操作

选择和创建数据库

选择和创建数据库的语法格式,如果数据库不存在则自动创建,例如,以下语句创建 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字符串:

  1. 不能是空字符串(“”)。
  2. 不得含有’ '(空格)、.、$、/、\和\0 (空字符)。
  3. 应全部小写。
  4. 最多64字节。

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

数据库的删除(删除当前数据库)

db.dropDatabase()

集合操作

集合的命名规范:

  1. 集合名不能是空字符串""。
  2. 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
  3. 集合名不能以"system."开头,这是为系统集合保留的前缀。
  4. 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。

查看当前库中的表:show tables命令

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)})

文档CRUD

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

文档的插入

单个文档插入

使用insertOne() 或 insert() (不推荐)或save() (弃用)方法向集合中插入文档,语法如下:

db.collection.insertOne(
	<document or array of documents>,
	{
		writeConcern: <document>,
		ordered: <boolean>
	}
)

参数:
mongodb基础_第5张图片

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,或不写该字段。

注意:

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

文档键命名规范:

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

批量插入

语法:

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])

参数:
mongodb基础_第6张图片
查询所有:

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

参数:
mongodb基础_第7张图片

  1. 覆盖的修改:

    如果想修改_id为1的记录,点赞量为1001,输入以下语句:

    db.comment.update({_id:"1"},{likenum:NumberInt(1001)})
    

    执行后会发现,这条文档除了likenum字段其它字段都不见了

  2. 局部修改:
    为了解决这个问题,需要使用修改器$set来实现,命令如下:

    db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})
    
  3. 批量的修改
    更新所有用户为 1003 的用户的昵称为 凯撒大帝 。

//默认只修改第一条数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒2"}})
//修改所有符合条件的数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})

提示:如果不加后面的参数,则只更新符合条件的第一条记录

  1. 列值增长的修改
    如果我们想实现对某列值在原有值的基础上进行增加或减少,可以使用 $inc 运算符来实现。
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的方法,用于获取集合中符合条件的文档数量。然而,它们在计算文档数量时存在一些区别。

  1. collection.estimated_document_count(): 这个方法返回的是集合中所有文档的估计数量。它是一个快速的近似计数方法,不会真正遍历集合中的每个文档。这个方法通常用于获取一个大致的文档数量,而不需要非常精确的结果。它比较适用于大型集合,因为它不需要扫描所有文档来计算数量,而是使用一些统计信息或近似算法进行估算。

  2. 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()的方法:

mongodb基础_第8张图片

聚合查询

管道
  1. 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。
  2. MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。
  3. 管道操作是可以重复的。
聚合管道操作

可参考菜鸟文档:菜鸟文档

命令 功能描述
$project 指定输出文档里的字段.
$match 选择要处理的文档,与fine()类似。
$limit 限制传递给下一步的文档数量。
$skip 跳过一定数量的文档。
$unwind 扩展数组,为每个数组入口生成一个输出文档。(把列表拆开)
$group 根据key来分组文档。
$sort 排序文档。
$geoNear 选择某个地理位置附近的的文档。
$out 把管道的结果写入某个集合。
$redact 控制特定数据的访问。
$lookup 多表关联(3.2版本新增)
$lookup

相当关系型数据库中多表关联查询

属性 作用
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: "起别名" 
 	} 
})
实操
  1. 准备表1:
    mongodb基础_第9张图片
  2. 准备表2:
    在这里插入图片描述
  3. 查询:
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

$exists是一个查询操作符,用于检查字段是否存在于文档中。它可以用于查找具有特定字段的文档或者查找没有特定字段的文档。

语法:

{ field: { $exists: <boolean> } }

用法:

存在db.person.find( { phone: { $exists: true } } )

注意:

  1. $exists操作符并不关心字段的实际值,只关心字段是否存在。如果指定的字段存在于文档中,不论其值是什么,都会被匹配到。

  2. 可以将$exists操作符与其他查询操作符组合使用,以构建更复杂的查询条件。

  3. 对于在嵌套文档或数组中的字段,$exists操作符只会检查字段是否存在于文档中,而不会检查字段的值是否为空或非空。如果需要同时检查字段是否存在和其值是否为空,可以结合使用$exists和其他查询操作符,如$eq$ne等。

$mod

$mod是一个查询操作符,用于匹配字段值与指定除法操作的余数条件,

语法:

{ field: { $mod: [ divisor, remainder ] } }

field是要匹配的字段名,divisor是除数,remainder是余数。

# 查询age字段的值除以2余0的文档
余数db.person.find( { age: { $mod: [ 2, 0 ] } } )

$mod操作符只能用于整数字段,并且它只能匹配满足给定除法操作的整数余数条件的文档。

$unset

$unset是MongoDB的更新操作符之一,用于从文档中移除指定的字段。

语法:

{ $unset: { field1: "", field2: "", ... } }

field1、field2等是要移除的字段名,空字符串 “” 用于表示移除该字段。

# 删除某个字段 
db.person.update( { _id: 1}, { $unset: { name:"" } })
  1. $unset操作符只能用于更新操作(如updateOne、updateMany等),它不适用于查询操作。

  2. 如果指定的字段不存在于文档中,$unset操作符不会引发错误,它会忽略该字段。

$type

$type是MongoDB的查询操作符之一,用于匹配指定字段的数据类型。

$type的使用方式如下:

{ field: { $type: <type> } }

在上述示例中,field是要匹配的字段名,是一个数值或字符串,用于指定要匹配的数据类型。

# 使用$type操作符来查找"price"字段类型为double的文档:
db.products.find({ price: { $type: "double" } })

MongoDB支持的数据类型包括:

  1. double:双精度浮点数
  2. string:字符串
  3. object:嵌套文档
  4. array:数组
  5. binData:二进制数据
  6. objectId:对象ID
  7. bool:布尔值
  8. date:日期
  9. null:空值
  10. regex:正则表达式
  11. javascript:JavaScript代码
  12. int:整数
  13. timestamp:时间戳
  14. long:长整数
  15. decimal:高精度小数
  16. minKey:最小键
  17. maxKey:最大键
$size

$size是MongoDB的查询操作符之一,用于匹配数组字段的大小(元素数量)。

{ field: { $size: <size> } }

field是要匹配的数组字段名,是一个整数,用于指定要匹配的数组大小。

使用$size操作符来查找"items"数组大小为3的文档:

db.orders.find({ items: { $size: 3 } })
  1. $size操作符只能用于匹配数组字段的大小,不适用于其他数据类型。

  2. 另外,对于嵌套数组,$size操作符会匹配整个数组的大小,而不仅限于顶层数组。

$mul

$mul是MongoDB的更新操作符之一,用于将指定字段的值乘以给定的因子。

{ $mul: { field: <factor> } }

field是要更新的字段名,是一个数字,表示要乘以的因子。

可以使用$mul操作符将"price"字段的值乘以2:

db.inventory.updateMany({}, { $mul: { price: 2 } })

$mul操作符只能用于更新操作(如updateOne、updateMany等),它不适用于查询操作。

$rename

$rename是MongoDB的更新操作符之一,用于重命名文档中的字段。

{ $rename: { oldField: newField } }

oldField是要重命名的字段名,newField是字段的新名称。

可以使用$rename操作符将"name"字段重命名为"fullName":

db.users.updateMany({}, { $rename: { name: "fullName" } })

$rename操作符只能用于更新操作(如updateOne、updateMany等),它不适用于查询操作。

另外,如果指定的旧字段不存在于文档中,$rename操作符不会引发错误,它会忽略该字段。

$elemMatch

$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

$push是MongoDB的更新操作符之一,用于向数组字段中添加一个或多个元素。

{ $push: { field: <value> } }

field是要更新的数组字段名,是要添加到数组中的元素。

假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$push操作符将新的喜爱项添加到"favorites"数组中:

db.users.updateOne({ _id: ObjectId("...") }, { $push: { favorites: "Book" } })
  1. 如果指定的数组字段不存在,$push操作符将会自动创建该数组字段,并将元素添加到其中。

  2. 另外,$push操作符还可以添加多个元素到数组中,可以使用$each修饰符和数组来指定要添加的多个元素。

  3. 不去重

$pull

$pull是MongoDB的更新操作符之一,用于从数组字段中移除满足指定条件的元素。

{ $pull: { field: <condition> } }

field是要更新的数组字段名,是一个查询条件,用于指定要移除的元素条件。

假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$pull操作符移除"favorites"数组中的特定元素:

db.users.updateOne({ _id: ObjectId("...") }, { $pull: { favorites: "Book" } })

$pull操作符可以移除满足指定条件的所有元素,而不仅仅是第一个匹配的元素。

$pop

$pop是MongoDB的更新操作符之一,用于从数组字段中移除第一个或最后一个元素。

{ $pop: { field: <value> } }

field是要更新的数组字段名,是一个数值,用于指定要移除的元素位置。若 为 1,则移除最后一个元素;若 为 -1,则移除第一个元素。

假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$pop操作符从"favorites"数组中移除第一个或最后一个元素:

db.users.updateOne({ _id: ObjectId("...") }, { $pop: { favorites: 1 } })
  1. 使用$pop操作符时,若数组字段不存在或为空,不会引发错误,而是忽略操作。

  2. 另外,可以在同一个更新操作中多次使用$pop操作符来移除多个元素。

$all

$all是MongoDB的查询操作符之一,用于匹配包含多个元素的数组字段。

{ field: { $all: [<value1>, <value2>, ...] } }

field是要匹配的数组字段名,, , ... 是要匹配的多个元素。

假设有一个名为"products"的集合,其中的文档包含了"tags"字段,它是一个数组,用于标记产品的标签。可以使用$all操作符来查找包含指定标签的产品:

db.products.find({ tags: { $all: ["electronics", "smartphone"] } })
$addToSet

$addToSet是MongoDB的更新操作符之一,用于向数组字段中添加一个元素,但仅在该元素不存在于数组中时才添加。

{ $addToSet: { field: <value> } }

field是要更新的数组字段名,是要添加到数组中的元素。

假设有一个名为"users"的集合,其中的文档包含了"favorites"字段,它是一个数组,用于存储用户的喜爱项。可以使用$addToSet操作符将新的喜爱项添加到"favorites"数组中,但只有当该元素在数组中不存在时才会添加:

db.users.updateOne({ _id: ObjectId("...") }, { $addToSet: { favorites: "Book" } })
  1. 如果指定的数组字段不存在,$addToSet操作符将会自动创建该数组字段,并将元素添加到其中。

  2. 另外,$addToSet操作符还可以添加多个元素到数组中,可以使用$each修饰符和数组来指定要添加的多个元素。

ObjectId

对于"_id" : ObjectId("5b151f8536409809ab2e6b26"),其组成如下:

  1. “5b151f85” 代指的是时间戳,这条数据的产生时间(0-8字节)
  2. “364098” 代指某台机器的机器码,存储这条数据时的机器编号(9-14字节的机器标识符)
  3. “09ab” 代指进程ID,多进程存储数据的时候,非常有用的(15-18字节的进程id)
  4. “2e6b26” 代指计数器,这里要注意的是,计数器的数字可能会出现重复,不是唯一的(19-24字节是计数器)
  5. 只要是支持MongoDB的语言,都会有一个或多个方法,对ObjectID进行转换可以得到以上四种信息
  6. 这个类型是不可以被JSON序列化的

索引-Index

  1. 索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档,以选择与查询语句匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
  2. 如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。
  3. 索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外MongoDB还可以使用索引中的排序返回排序结果。
  4. 官网文档:https://docs.mongodb.com/manual/indexes/
  5. MongoDB索引使用B树数据结构(确切的说是B-Tree,MySQL是B+Tree)

索引的类型

单字段索引

MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)。

对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。

mongodb基础_第10张图片

复合索引

MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)。

复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后在每个userid的值内,再在按score倒序排序。

mongodb基础_第11张图片

其他索引

  1. 地理空间索引(Geospatial Index)、文本索引(Text Indexes)、哈希索引(Hashed Indexes)。
  2. 地理空间索引(Geospatial Index):为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
  3. 文本索引(Text Indexes):MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”),而将集合中的词作为词干,只存储根词。
  4. 哈希索引(Hashed Indexes):为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询。

索引的管理操作

索引的查看

db.collection.getIndexes()

查看comment集合中所有的索引情况:

> db.comment.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "articledb.comment"
	}
]

结果中显示的是默认 _id 索引:

  1. MongoDB在创建集合的过程中,在_id 字段上创建一个唯一的索引,默认名字为 _id,该索引可防止客户端插入两个具有相同值的文档,您不能在_id字段上删除此索引。
  2. 注意:该索引是唯一索引,因此值不能重复,即_id值不能重复的。在分片集群中,通常使用 _id 作为片键。

索引的创建

db.collection.createIndex(keys, options)

参数:
mongodb基础_第12张图片
options(更多选项)列表:
mongodb基础_第13张图片
单字段索引示例:对 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直接从索引返回结果,而不扫描任何文档或将文档带入内存。 这些覆盖的查询可以非常有效。

mongodb基础_第14张图片

mongo导出导入

导出mongoexport

mongoexport -h ip:port -u 用户名 -p 密码-d 数据库名 -c 集合名 -o 输出的文件路径以及文件名 --type json/csv -f 字段名

  1. 如果不指定type,默认为json
  2. type如果为csv,则需要指明字段:-f “name,id,…”

导入mongoimport

mongoimport -d "指定要被导入数据的数据库名" -c "指定表名" --file "指定被导入的文件名" --headline --type "指定类型" -f "指定字段"

  1. 如果导入的格式是csv,则可以使用第一行的标题作为导入的字段,–headline
  2. type默认为json

备份mongodump

mongodump -h "数据库所在ip地址" -d "数据库名称" -o "保存文件的目录"

恢复mongorestore

mongorestore -h "数据库所在ip" -d "要保存数据的数据库名称" --dir "存放数据的目录"

认证

  1. -u 用户名 -p pwd
  2. –authenticationDatabase admin

mongoimport导入json数据阻塞,导入不完全

  1. mongoexport导出json文件
  2. mongoimport导入文件时 卡到某个进度不再动

解决方法:

方法一:pymongo

读取文件再利用pymongo写入操作,但此方法容易有编码,bson格式,时间格式等错误

方法二:numInsertionWorkers

这个参数设置worker的数量,可以理解为并发数,设置--numInsertionWorkers 100或者更多,数据就可以完全导入,但最后还是会卡在100%

如果只加workers数量不行的话,可以加--batchSize 10000步长试试。

GridFS

MongoDB GridFS

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工具
mongodb基础_第15张图片

具体操作

  1. 存储文件:mongofiles –d database put filename
    在这里插入图片描述
    mongodb基础_第16张图片

  2. 获取文件:mongofiles –d database -l new_filename get filename
    在这里插入图片描述

  3. 删除文件:mongofiles –d database delete filename
    在这里插入图片描述

  4. 存储文件:mongofiles –d database -l filename put new_filename
    以这种命令上传文件,文件名为new_filename,不会携带路径

  5. 查看文件列表:mongofiles -d database list
    在这里插入图片描述

  6. 查找文件: mongofiles -d database search test.txt
    在这里插入图片描述

pymongo操作

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')
    

原子操作&事务

原子操作

  1. 原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
  2. 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch).

事务

  1. 事务(Transaction)是访问并可能更新数据库中各项数据项的一个程序执行单元(unit)。 事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
  2. 事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
  3. 事务结束有两种,事务中的步骤全部成功执行时,提交事务。如果其中一个失败,那么将会发生回滚操作,并且撤销之前的所有操作。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。
  4. 事务是恢复和并发控制的基本单位。
  5. 事务具有四个特征:原子性、一致性、隔离性和持久性。这四个特征通常称为ACID。

官方文档:

事务(GO)
在这里插入图片描述

支持版本

  1. MongoDB从 3.0版本引入WiredTiger存储引擎之后开始支持事务。
  2. MongoDB 3.6之前的版本只能支持单文档的事务。
  3. MongoDB 4.0版本开始支持复制集部署模式下的事务。
  4. MongoDB 4.2版本开始支持分片集群中的事务。

要获取事务支持则需要安装对应的或高版本的mongodb和驱动(pymongo(python),mongo-driver(go))

内置的一些原子操作

在这里插入图片描述
在这里插入图片描述

事务支持

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
		}
	}
}

python

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方法

go

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("断言失败")
	}
}

python

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)

你可能感兴趣的:(mongodb,mongodb,数据库,nosql)