Mongodb Manual阅读笔记:CH2 Mongodb CRUD 操作
发布日期:2021-11-11 09:16:12 浏览次数:3 分类:技术文章

本文共 13743 字,大约阅读时间需要 45 分钟。

2 Mongodb CRUD 操作

 

 

mongodb提供了创建,读取,修改,删除简称CRUD

 

2.1 Mongodb CRUD简介

mongodb存储的文档是和json格式类似的,但是mongodb存储的是BSON也就是,以2进制方式保存JSON格式数据。在mongodb中文档相当于记录,而存文档的Collection相当于表。

2.1.1数据库操作

2.1.1.1查询

Mongodb可以对一个collection通过条件来过滤文档,并且可以用projection选择输出

2.1.1.2数据修改

创建,更新,删除数据,只能修改单个collection(不包含嵌入方式)

2.1.2 相关特性

2.1.2.1索引

Mongodb可以顺序的表示,也可以强制唯一性保存,mongodb中索引也是用b树保存,

2.1.2.2读偏好

读偏好主要是用在复制集群中,从哪个实例中读取。

2.1.2.3写注意(write concern

不同的写注意保证了不同的数据写入要求,级别越低写入越快,但是写入安全性越低

2.2 Mongodb CRUD概述

读操作:游标,查询优化,分布式查询

写操作:写注意,分布式写操作

2.2.1读操作

查询接口

         Mongodb通过提供的接口db.collection.find()方法来查询数据,会返回一个游标

查询行为

         1.所有的查询只能针对一个collection

         2.可以通过limitsskipssort来影响查询结果

         3.sort()来范围查询的顺序

         4.update修改数据和select的条件语法一样

         5.aggregation(聚合)管道中,$match管道提供了对mongodb的访问

查询语句

Projection

         Projection用来限制输出那些字段,_id是默认输出的。

         Projection行为

                   1._id会默认出现,除非显示设置

                   2.对于包含array的字段,提供了$elemMatch,$slice,$等操作符

                   3.对于聚合,使用$Project来处理

2.2.1.1游标

         mongodb查询db.collection.find()返回一个游标,若没有分配给一个变量直接运行20

         游标行为对于游标默认不活动时间超过10分钟就会被销毁,当然可以指定noTimeout标记,来先限制等待。

         游标隔离级别游标在有效期内是不隔离的,若有写入操作就会影响,可以使用快照模式来处理

         游标Batch数据从mongodb到客户端是以batch的方式,batch的大小最大不超过最大BSON文档的大小,对于很多查询而言,第一个batch返回101个文档,或者1MB的大小,之后是4MB

         对于包含sort的查询但是没有索引,mongodb会导入到内存然后排序,会在第一个batch的时候返回所有的结果。用cursor.hasnext()cursor.next()的组合来看有没有下一个batct,获取下一个batch

         游标信息通过命令cursorInfo来获取游标的信息:1.打开的游标总数,2.当前使用的客户端游标大小,3.从服务重启来,超时的游标数。

2.2.1.2优化查询

         索引和其他数据库中的一样,减少了没必要数据的查询。

         创建索引来支持查询使用db.collection.ensureIndex()来创建索引

         索引的选择度某些查询操作时选择度很差,比如$nin,$ne,高选择度的才可以更好的使用索引,$regex      也是没有办法使用索引的。

         覆盖查询覆盖查询的条件:1.所有的字段都是索引的一部分,2.所有返回的字段也在索引中

2.2.1.3查询计划

Mongodb优化器处理查询,并根据索引选择一个最有效的索引,当collection的数据量被改变优化器会偶尔重建计划,可以使用explain()来查看执行计划。

         查询优化

优化器创建计划的过程:1.通过不同的索引,并发的执行查询,2.记录负荷的结果到buffer中,3.当非排序的查询计划全部返回,或者顺序的执行计划全部返回,或者顺序的执行计划返回了超过了一个指标,那么会停止其他执行的计划并选择这个计划

         计划重建

当发生以下事件,计划会被重建:1.超过1000个写入,2.重建索引,3.添加或者删除索引,4.服务进程重启

2.2.1.4分布式查询

         Sharded集群中读Sharded允许数据分区,并对应用程序透明,查询会通过路由到达相关的实例中,通过shard key若没有shard key,查询会分发到索引的shard实例中取执行。

         从复制集群中读,在复制集群中使用读偏好,来决定从哪个实例中读取数据,读偏好设置有一下几个好处:1.减少延迟,2.提供读的带宽,3.备份作用,4.容灾切换。同时也要注意,主库和分库之间的延迟,分库不能代表当前主库的数据状态。

2.2.2写操作

和读一样写操作只能用在一个collection上。

创建

         通过接口,db.collection.insert()collection上创建一个文档,若update接口在有upset的标记下,也可以实现创建文档的功能。

插入的行为

         如果你插入时,没有指定_id那么mongodb会分配一个,若指定了_id需要保持在collection中是唯一的。

更新

         通过接口,db.collection.update()来实现更新操作,也可以用save

  

更新行为

         update默认只对一行进行更新,若要对多行进行更新要使用multi选项,通没有指定类似set的参数,就会把所有影响的文档替换,当更新是,文档的大小超过了原先分配的大小,那么会重新分配空间,然后把文档放入,这样会导致,文档内的字段重排。

删除

         删除通过接口,db.collection.delete()实现。

删除的行为

         默认使用delete不太条件,会把所以的文档全部删除

写操作的隔离性

         修改一个文档,可以支持原子性,但是对多个文档的修改就不支持原子性,尽管如此单还是可以使用隔离操作(isolaation_operator)。也可以通过人工方式实现2阶段提交,来保证隔离性。

2.2.2.1写注意(write concern)

         弱写注意,可能会导致一些错误,因为不需要等服务端返回写入成功,强写注意,client可以等待写入完成。db.runCommand( { getLastError: 1, w: 2 } )来配置。

 

         写注意级别:忽略错误(Error Ingored),最低级的级别,mongodb不会通知写入操作完成,包含        发生错误,性能最佳,但是会影响写入数据的一致性和持久性,w:-1

         不通知mongodb不会通知写入成功或者失败,但是当发生错误是,还是会尽力接受和处理错误,可以发现由系统网络配置导致的网络错误。w:0

         通知:可以取回一个写入操作信息,这个级别,允许客户端抓取网络,重复key等错误,w:1mongodb默认使用这个级别,写注意默认使用无参数的getLastError,对于复制集群可以在getLastErrorDefaults中定义默认的写注意,若没有定义默认使用getLastError取通知.

         日志:使用这个级别,只有在提交,写入日志后,服务才会通知写入操作,这样可以保证服务崩溃后数据库的恢复。使用这个操作要指定w:1,j:true

         复制通知:使用这个级别可以保证,写操作写入到了复制集群,w:>1,在复制集群下,只要再设置j:true,主库就可以用日志写注意级别。

2.2.2.2分布式写

         sharded集群中写:可以根据shard key来对分区插入数据,可以提高写入性能,若没有key,会被广播到所有的shard,若数据库只插入到一个key,那么可能照成单点性能问题。

         在复制集中写:在复制集中写入,会被写入到primary上,然后记录在oplog或者log中,然后根据oplog分发到分库中,对于密集的写入操作会导致分库和主库之间严重的延迟,虽然可以通过写注意保证一致性,但是会造成写入性能问题

2.2.2.3写操作性能

         索引:和其他数据库一样,写操作会修改索引,会带来一些性能消耗。

         文档增长update 操作文档的空间时,会分配一个新空间,然后把文档复制进去,这样会加大update的时间,in-place比文档增长来的有效。

2.2.2.4存储性能

         硬件:很多元素和存储系统有关,影响着服务的性能,如,随机读写性能,磁盘cache,预读,RAID级别。

         日志mongodb使用顺序写方式来记录日志,日志提供了一致性和crash弹性,考虑一下方面来增加日志性能:1.使用独立的设备,2.若写注意级别为日志,mongodb通过减少提交间隔来减少写负荷,3.通过手动配置journalCommitInterval来减少提交间隔。

2.2.2.5大数据量写入

         使用insert方法insert方法通过传入一个数组来执行大数据量写入。通过写注意的设置大数据量写入,可以显著的提高性能。并且通过ContinueOnError选项可以让批量插入有错误下还能完成。

         Shard集群中ContinueOnError只在非分片中有效,这类大批量写入,对sharded集群来说会降低性能若要写入考虑一下方案:

                  预分配splits:大批量导入中,若collection是空的,那么只有一个spilt,当插入时    重新分配split导致性能问题所以要预先split

                   避免单调增长:可能大批量插入只针对一个sharded key,这样sharded的性能就没        有办法体现,如无法避免,可以考虑:1.二进制方式翻转shard key2.key的前16位和  16位互换。

2.2.2.6行留白(Record Padding

         因为超出文档就会重新分配,为了避免这个问题,可以事先留白,减少重分配的可能性。

         留白因子:可以使用db.collection.stats()查看当前的paddingFactor,留白的大小可以通过一个公式计算:padding size = (paddingfactor-1)*<document size>。留白并不能对每个文档做准确的设置,只能根据文档大小的平均数。留白的大小只能通过执行compact或者repairDatabase选项回收。

         删除留白:可以通过compactrepairDatabase和初始化复制同步选项来移除,导出导入也可以。

         行分配策略:为了更加有效的使用因为删除而空闲的空间,或者文档重新分配的空间,可以指定mongodb分配的行大小,大小为2n次。

2.3 Mongodb CRUD教程

2.3.1 插入文档

mongodb中,可以使用insertupdatesave来插入数据

2.3.1.1使用insert插入文档

inventory插入数据:db.inventory.insert( { _id: 10, type: "misc", item: "card", qty: 15 } )

2.3.1.2使用update插入文档

使用update插入文档的时候要指明upserttrue,表示如果有数据则修改,没数据则插入

db.inventory.update(

{ type: "book", item : "journal" },

{ $set : { qty: 10 } },

{ upsert : true }

)

{ "_id" : ObjectId("51e8636953dbe31d5f34a38a"), "item" : "journal", "qty" : 10, "type" : "book" }

插入的文档如果没有指定_id mongodb会自动分配一个_id

2.3.1.3使用save插入文档

inventory插入数据db.inventory.save( { type: "book", item: "notebook", qty: 40 } )

{ "_id" : ObjectId("51e866e48737f72b32ae4fbc"), "type" : "book", "item" : "notebook", "qty" : 40 }

2.3.2查询文档

mongodb中,find()返回一个游标,通过游标来获取返回的文档。

2.3.2.1 查询全部的文档

可以使用如下来查询所有的文档:

db.inventory.find( {} )db.inventory.find()

2.3.2.2指定条件查询文档

如果要在inventory中查询typefood或者snacks的:

db.inventory.find( { type: { $in: [ 'food', 'snacks' ] } } )

2.3.2.3使用and条件查询

mongodb中,只要都好分开就表示and条件

db.inventory.find( { type: 'food', price: { $lt: 9.95 } } )

2.3.2.4 使用or条件查询

db.inventory.find(

{ $or: [

{ qty: { $gt: 100 } },

{ price: { $lt: 9.95 } }

]

}

)

2.3.2.5使用andor 查询

db.inventory.find( { type: 'food', $or: [ { qty: { $gt: 100 } },

{ price: { $lt: 9.95 } } ]

} )

2.3.2.6子文档

当要查询子文档的时候,可以指定子文档的所有值,也可以使用.(点号) 获取子文档的字段值。

精确的匹配文档

也就是匹配整个子文档

db.inventory.find(

{

producer: {

company: 'ABC123',

address: '123 Street'

}

}

)

使用子文档的字段匹配

用点号获取子文档的值

db.inventory.find( { 'producer.company': 'ABC123' } )

2.3.2.7数组

匹配方式和子文档类似

匹配整个数组

db.inventory.find( { tags: [ 'fruit', 'food', 'citrus' ] } )

匹配某个元素

只要某个元素在数组中都会被匹配

db.inventory.find( { tags: 'fruit' } )

匹配指定元素

db.inventory.find( { 'tags.0' : 'fruit' } )

子文档数组

使用数组所有匹配子文档中的字段:db.inventory.find( { 'memos.0.by': 'shipping' } )

多字段匹配

db.inventory.find(

{

'memos.memo': 'on time',

'memos.by': 'shipping'

}

)

2.3.3限制查询返回的字段

Projection可以限制字段的输出

2.3.3.1 返回所有字段

Find,函数第二个参数就是projection如果不填就是全部返回

2.3.3.2返货指定字段和_id

db.inventory.find( { type: 'food' }, { item: 1, qty: 1 } )

其中_id字段是默认返回的所有不用指定

2.3.3.3只返回指定字段

db.inventory.find( { type: 'food' }, { item: 1, qty: 1, _id:0 } )

因为_id是默认返回的,所以要设置_id不返回。

2.3.3.4 返回除某字段外的所有字段

db.inventory.find( { type: 'food' }, { type:0 } )

2.3.3.5数组字段的Projection

对于数组来说唯一可用的projection操作就是$elemMath$slice

2.3.4mongo shell中迭代一个游标

find函数没有赋值给变量,在shell中会直接迭代20次。

2.3.4.1手动迭代游标

通过以下方式可以直接迭代20

var myCursor = db.inventory.find( { type: 'food' } );

myCursor

也可以手动迭代

var myCursor = db.inventory.find( { type: 'food' } );

var myDocument = myCursor.hasNext() ? myCursor.next() : null;

if (myDocument) {

var myItem = myDocument.item;

print(tojson(myItem));

}

还可以用forEach方法

var myCursor = db.inventory.find( { type: 'food' } );

myCursor.forEach(printjson);

2.3.4.2迭代索引

mongo shell中可以可以直接把游标toArray(),然后直接访问数组即可。

var myCursor = db.inventory.find( { type: 'food' } );

var documentArray = myCursor.toArray();

var myDocument = documentArray[3];

2.3.5分析查询性能

Explain()游标方法,这个方法用来分析查询的效率,确定如何使用索引。

2.3.5.1 评估查询性能

db.inventory.find( { type: 'food' } ).explain()

结果

{

"cursor" : "BtreeCursor type_1",

"isMultiKey" : false,

"n" : 5,

"nscannedObjects" : 5,

"nscanned" : 5,

"nscannedObjectsAllPlans" : 5,

"nscannedAllPlans" : 5,

"scanAndOrder" : false,

"indexOnly" : false,

"nYields" : 0,

"nChunkSkips" : 0,

"millis" : 0,

"indexBounds" : { "type" : [

[ "food",

"food" ]

] },

"server" : "mongodbo0.example.net:27017"

}

Cursor的值为btreecursor表示是用了索引

n表示返回的文档数

2.3.5.2比较索引性能

如果要比较索引之间的性能可以使用hint来强制

db.inventory.find( { type: 'food' } ).hint( { type: 1 } ).explain()

db.inventory.find( { type: 'food' } ).hint( { type: 1, name: 1 } ).explain()

2.3.6修改文档

修改文档的操作有,update saveupdate是修改文档,save如果文档存在则替换文档。

2.3.6.1使用update修改多个文档

要修改多个文档时,要把multi-true设置上。

db.inventory.update(

{ type : "book" },

{ $inc : { qty : -1 } },

{ multi: true }

)

2.3.6.2使用save修改文档

Save可以替换已经存在的文档

db.inventory.save(

{

_id: 10,

type: "misc",

item: "placard"

}

)

2.3.7删除文档

删除文档一般使用remove方法

2.3.7.1删除所有文档

直接写不带参数的remove就会把collection中的文档全部删除。

db.inventory.remove()

2.3.7.2 带条件删除文档

db.inventory.remove( { type : "food" } )

2.3.7.3 带条件删除一个文档

db.inventory.remove( { type : "food" }, 1 )

2.3.8 执行二阶段提交

当处理多文档的更新和事务,可以使用二阶段提交的方式来写多个文档

2.3.8.1背景

单文档操作是原子的,多文档没有,所以多文档的操作不能有原子性保障,当执行事务时就可能发生以下问题:

1.原子性:如果一个操作失败,事务必须回滚到之前的状态

2.隔离性:同步发生的事务,在事务过程中必须看到一致性的数据

3.一致性:如果发生数据库错误,中断了事务,数据库必须能够恢复到一致的状态

多文档的写可以使用二阶段提交

2.3.8.2模式

概述

假设要做转账操作,我们有一个accounts collection和一个transactions collection用来保存用户数据和事务数据。

db.accounts.save({name: "A", balance: 1000, pendingTransactions: []})

db.accounts.save({name: "B", balance: 1000, pendingTransactions: []})

事务描述

设置事务初始化状态:在创建的 transactions中插入一条数据并且状态为initial

db.transactions.save({source: "A", destination: "B", value: 100, state: "initial"})

然后把事务状态设置为pending:在要处理事务之前把事务状态设置为pending表示处理这个事务。db.transactions.update({_id: t._id}, {$set: {state: "pending"}})

应用事务到2个账号上

db.accounts.update({name: t.source, pendingTransactions: {$ne: t._id}}, {$inc: {balance: -t.value}, $push: {pendingTransactions: t._id}})

db.accounts.update({name: t.destination, pendingTransactions: {$ne: t._id}}, {$inc: {balance: t.value}, $push: {pendingTransactions: t._id}})

更新accountpendingTransaction中没有t._id的事务,就是t._id不是已经在处理事务,然后设置值,并把设置上pendingTransaction

设置事务提交状态db.transactions.update({_id: t._id}, {$set: {state: "committed"}})

删除pending事务

db.accounts.update({name: t.source}, {$pull: {pendingTransactions: t._id}})

db.accounts.update({name: t.destination}, {$pull: {pendingTransactions: t._id}})

设置事务完成

db.transactions.update({_id: t._id}, {$set: {state: "done"}})

从错误场景中恢复

可能会出现错误的场景:

1.在初始化事务,应用事务到2个账号上之间出错,为了恢复应用程序应该获取pending状态的事务,然后继续处理。

2.发生在应用事务,事务完成之间的错误,为了恢复应用程序应该获取done状态的事务,然后继续处理。

回滚:某些场景就需要回滚操作

1.当应用事务之后,已经提交了事务,就不应该去回滚事务,而是重新创建一个新的事务,来交换数据

2.在创建事务后,但是没有应用事务,可以使用一下步骤:

设置事务状态为取消db.transactions.update({_id: t._id}, {$set: {state: "canceling"}})

Undo事务

db.accounts.update({name: t.source, pendingTransactions: t._id}, {$inc: {balance: t.value}, $pull: {pendingTransactions: t._id}})

db.accounts.update({name: t.destination, pendingTransactions: t._id}, {$inc: {balance: -t.value}, $pull: {pendingTransactions: t._id}})

把事务状态修改为已取消db.transactions.update({_id: t._id}, {$set: {state: "canceled"}})

多应用程序:多应用程序的时候很容易造成一致性问题和冲突。在这种情况下,在transaction中加入application字段,来区别应用程序。并且使用findAndModify()

t = db.transactions.findAndModify({query: {state: "initial", application: {$exists: 0}},

update: {$set: {state: "pending", application: "A1"}},

new: true})

2.3.8.3在生产环境使用二阶段提交

在例子中,我们假设了一些东西比如:

1.总是可以回滚一个用户

2.账号的剩余可以为负数

但是在生产环境下需要考虑:

1.在把事务设置为pending的时候,需要查看账号是否有足够的资金。

2.当事务设置提交时,需要修改借贷方

当然这些操作时在一个文档里面的,是原子的

也需要考虑:

1.数据库接口的写注意(wirte concern)

2.mongodjournaling是否开启确保数据库非干净关闭后数据库状态的恢复。

2.3.9 创建tailable cursor

2.3.9.1概述

默认游标使用完之后就会被关闭,但是Tailable Cursor不会,当文档有插入,Tailable Cursor也可以取到。对于高写入的capped collection上使用tailable cursor太昂贵。

Tailable cursor有一下特点:

1.游标不会使用索引,以自然顺序返回

2.tailable cursor在初始化阶段比较昂贵,新增的文档代价并不是很大。

3.某些情况下tailable curos会不可用,没有返回的文档,返回了collection的末尾,然后被应用程序删掉了。

2.3.9.2 C++例子

看手册p75

2.3.10隔离操作顺序

看手册p76

2.3.11创建自增字段

本节介绍2个创建自增的方法,计数collection,乐观循环(optimistic loop

2.3.11.1计数Collection

使用一个独立的计数Collection来跟踪最新的顺序,_id表示顺序的名,seq表示最新的顺序。

1.插入一个userid的初始化值到计数collection

db.counters.insert(

{

_id: "userid",

seq: 0

}

)

2.创建一个getNextSequence函数来获取一个名字的下一个顺序值。

function getNextSequence(name) {

var ret = db.counters.findAndModify(

{

query: { _id: name },

update: { $inc: { seq: 1 } },

new: true

}

);

return ret.seq;

}

3.在插入时使用getNextSequence函数

db.users.insert(

{

_id: getNextSequence("userid"),

name: "Sarah C."

}

)

2.3.11.2 乐观循环

读者觉得很奇葩的方法,手册 p80

2.3.12 更新数组中限制更新行数

2.3.12.1 方式

students collection有一个文档

{

_id: 1,

scores: [

{ attempt: 1, score: 10 },

{ attempt: 2 , score:8 }

]

}

然后再$push里面使用$each$sort来实现

db.students.update(

{ _id: 1 },

{ $push: { scores: { $each : [

{ attempt: 3, score: 7 },

{ attempt: 4, score: 4 }

],

$sort: { score: 1 },

$slice: -3

}

}

}

)

输出结果

{

"_id" : 1,

"scores" : [

{ "attempt" : 3, "score" : 7 },

{ "attempt" : 2, "score" : 8 },

{ "attempt" : 1, "score" : 10 }

]

}

2.4 MongoDB CRUD 指南

2.4.1 查询游标方法

Cursor.count:返回文档的个数

Cursor.explain:显示查询的执行计划

Cursor.hint:强制mongo使用指定的索引

Cursor.limit:显示游标返回文档个数

Cursor.next:获取游标中下一个文档

Cursor.skip:跳过给定数量的文档

Cursor.sort:对指定字段排序

Cursor.toArray:游标返回成数组

2.4.2 查询和数据操作

Db.collection.count:返回collection文档个数

Db.collection.distinct:以数组方式返回指定字段的不同值

Db.collection.find:在collection执行find返回游标

Db.collection.findOne:返回一个文档

Db.collection.insert:在collection上创建文档

Db.collection.remove:删除collection上的文档

Db.collection.save:可以insert,也可以替换已经存在的文档

Db.collection.update:修改已经存在的文档

2.4.3 MongoDB CRUD指南文档

主要介绍,写注意(write concern),sql mongodb的对照表,mongodb的驱动和客户端lib

2.4.3.1 写注意(write concern)

概述

Write concernmongodb为写入提供的保障,但是是通过消耗性能来保证write的安全性。可以对数据的重要性不同分级。

可以用的write concern

提供write concern,写入操作之后,驱动使用getLastError来获取最后才做的信息。返回一个err字段,:

1.null表示写入成功

2.null就表示具体的错误

GetLastErroe的参数:

J或者journal选项:指定这个选项后,mongodb会写入日志到磁盘的journal上确保mongodb突然关闭之后数据不会丢失。

如下:db.runCommand( { getLastError: 1, j: "true" } )

如果在mongod中并没有启用journal,然后getLastError指定了 j:true会返回一个jnote字段包含了这个文档。

W选项:这个选项可以停用write concern,也可以指定复制集的write concern。可选值:

-1:关闭write operation通知

0:关闭基本的写入操作通知,但是返回socket异常和网络异常

1:提供通知,说明已经写入到了单个实例,或者复制集的primary

>1:说明要写入到>1个实例中。一般用户复制集,如果这个值大于复制集成员,mongodb会一致等下去。

Majority:确认写入操作已经传播到大多数的已配置复制集

Tag设置:使用tag设置可以很好的控制复制集成员很好的控制的不同write concern

getLastError还提供了一个超时,如果给0会一直等待。

2.4.3.2 sql mongodb的对照表

手册p85

2.4.3.3 mongodb驱动和客户端lib

手册p95

posted on
2014-01-20 17:36  阅读(
...) 评论(
...) 收藏

转载于:https://www.cnblogs.com/Amaranthus/p/3527305.html

转载地址:https://blog.csdn.net/dida4700/article/details/101644475 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Mongodb Manual阅读笔记:MongoDB教程
下一篇:Mongodb Manual阅读笔记:CH3 数据模型(Data Models)

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月17日 01时03分50秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章