MongoDB固定集合Capped Collections
//
在MongoDB中,如果我们想定时删除一部分集合内的数据,通常可以通过2中方法来实现,第一种是TTL索引,第二种就是固定集合。
今天我们重点看看固定集合。
01
固定集合的定义
通过名称的描述,不难看出,固定集合,肯定是某个属性是固定的,在MongoDB中,固定集合中的固定,指的是集合的大小。固定集合也就是指固定大小的集合。
随着数据的不断增多,固定集合的大小不会变化,这就意味着固定集合必须有数据的淘汰机制。
固定集合工作的方式,类似一个圆环。可以想象它将所有的文档存储在一个圆环中,一开始圆环是空的,数据循环写入圆环,一旦圆环被填满,那么后来的数据,将会覆盖先来的数据。类似mysql中的redo log。
02
固定集合的特性
1、插入顺序:
固定集合会自动记录插入顺序,固定集合上的查询不需要索引来保证顺序,也正是因为不需要使用和维护索引,所以固定集合的插入性能比较高。
2、自动删除旧文档:
固定集合会自动删除最旧的文档,而不需要借助第三方脚本或者程序。
3、使用场景;
典型的使用场景是写入日志,因为日志数据一般不需要持久化,属于消费性的。
oplog.rs就是利用了固定集合。如下命令:
sharding_yeyz:PRIMARY> db.oplog.rs.isCapped() true
但是它和一般的固定集合不同,它的大小可以超过指定的大小,之所以这么设计,是为了避免误删majority事务提交点。
03
固定集合的限制
1、如果你更新固定集合(通常不会对固定集合做更新操作),最好设置一个索引,从而避免全集合的扫描。
2、如果更新或者替换操作改变了集合的大小,那么这个操作会失败
3、你不能手工从固定集合中删除文档,如果想删除整个集合,请使用drop函数来执行
4、不能对固定集合做分片
5、使用自然排序可以有效地从集合中检索最近插入的元素。这(有点)类似于日志文件的tail操作
6、MongoDB4.2开始,不能在事务中将数据写入固定集合,当然,事务中读固定集合是允许的。
04
创建固定集合的方法
创建固定集合的方法如下:
db.createCollection("log", { capped : true, size : 5242880, max : 5000 } )
其中,size最小设置为4096,如果设置的值小于4096,则系统会自动将其设置为4096,size的值必须为256的倍数。size的单位是byte
max指的是文档记录的条数。
那么问题来了,如果max和size都被指定了,会以哪个为主呢?
答案是会先以size为准。如果size不符合结果,那么max参数将会被忽略。
验证过程如下:
> db.createCollection("yeyz", { capped : true, size: 1000, max : 1500 } ) { "ok" : 1 } > > for (var i=1 ;i<=100 ; i++){ db.yeyz.insert({"number":i})} WriteResult({ "nInserted" : 1 }) > > db.yeyz.find().count() 26
创建yeyz这个固定集合,size设置为1000b,max设置为100条,也就是说,最多1kb的空间,最多写入100条。
我们插入100个文档,显示插入成功。但是查询的结果是只写入了26个文档。
我们来看这26个文档的内容,如下:
> db.yeyz.find() { "_id" : ObjectId("600ee22fb97bfefc365ec210"), "number" : 75 } { "_id" : ObjectId("600ee22fb97bfefc365ec211"), "number" : 76 } { "_id" : ObjectId("600ee22fb97bfefc365ec212"), "number" : 77 } { "_id" : ObjectId("600ee22fb97bfefc365ec213"), "number" : 78 } { "_id" : ObjectId("600ee22fb97bfefc365ec214"), "number" : 79 } { "_id" : ObjectId("600ee22fb97bfefc365ec215"), "number" : 80 } { "_id" : ObjectId("600ee22fb97bfefc365ec216"), "number" : 81 } { "_id" : ObjectId("600ee22fb97bfefc365ec217"), "number" : 82 } { "_id" : ObjectId("600ee22fb97bfefc365ec218"), "number" : 83 } { "_id" : ObjectId("600ee22fb97bfefc365ec219"), "number" : 84 } ... { "_id" : ObjectId("600ee22fb97bfefc365ec227"), "number" : 98 } { "_id" : ObjectId("600ee22fb97bfefc365ec228"), "number" : 99 } { "_id" : ObjectId("600ee22fb97bfefc365ec229"), "number" : 100 }
可以发现,最后的结果是75~100这最后26个数字,说明前面写入的1~74都被覆盖了。
上面的例子演示了size不够情况下,插入数据的情况,如果我们将size扩大100倍,再来看结果:
> db.createCollection("yeyz1", { capped : true, size: 100000, max : 1500 } ) { "ok" : 1 } > for (var i=1 ;i<=100 ; i++){ db.yeyz1.insert({"number":i})} WriteResult({ "nInserted" : 1 }) > > db.yeyz1.find().count() 100 > db.yeyz1.find() { "_id" : ObjectId("600ee266b97bfefc365ec22a"), "number" : 1 } { "_id" : ObjectId("600ee266b97bfefc365ec22b"), "number" : 2 } { "_id" : ObjectId("600ee266b97bfefc365ec22c"), "number" : 3 } { "_id" : ObjectId("600ee266b97bfefc365ec22d"), "number" : 4 } { "_id" : ObjectId("600ee266b97bfefc365ec22e"), "number" : 5 } { "_id" : ObjectId("600ee266b97bfefc365ec22f"), "number" : 6 } { "_id" : ObjectId("600ee266b97bfefc365ec230"), "number" : 7 } { "_id" : ObjectId("600ee266b97bfefc365ec231"), "number" : 8 } { "_id" : ObjectId("600ee266b97bfefc365ec232"), "number" : 9 } { "_id" : ObjectId("600ee266b97bfefc365ec233"), "number" : 10 } { "_id" : ObjectId("600ee266b97bfefc365ec234"), "number" : 11 } { "_id" : ObjectId("600ee266b97bfefc365ec235"), "number" : 12 } { "_id" : ObjectId("600ee266b97bfefc365ec236"), "number" : 13 } { "_id" : ObjectId("600ee266b97bfefc365ec237"), "number" : 14 } { "_id" : ObjectId("600ee266b97bfefc365ec238"), "number" : 15 }
这次可以看到,size扩大成100000之后,我们的100条数据都写入了,而且没有出现数据覆盖的情况。
05
其他说明
1、反向查找
可以使用natural自然排序的逆序去找。
> db.yeyz.find().sort( { $natural: -1 } ) { "_id" : ObjectId("600ee22fb97bfefc365ec229"), "number" : 100 } { "_id" : ObjectId("600ee22fb97bfefc365ec228"), "number" : 99 } { "_id" : ObjectId("600ee22fb97bfefc365ec227"), "number" : 98 } { "_id" : ObjectId("600ee22fb97bfefc365ec226"), "number" : 97 } { "_id" : ObjectId("600ee22fb97bfefc365ec225"), "number" : 96 } { "_id" : ObjectId("600ee22fb97bfefc365ec224"), "number" : 95 } { "_id" : ObjectId("600ee22fb97bfefc365ec223"), "number" : 94 } { "_id" : ObjectId("600ee22fb97bfefc365ec222"), "number" : 93 }
2、将普通集合转换成固定集合
db.runCommand({"convertToCapped": "mycoll", size: 100000});
将普通集合转换成固定集合,仅可以使用size参数,max参数是无法使用的。
还有一点特别需要注意,在转换的过程中,当前数据库会被加排他锁,其他的在这个数据库上的需要加锁的操作都会被阻塞,直到当前操作完成。所以,这个操作进行的时候需要谨慎操作,最好在没有数据写入的时候,创建固定集合,而不是从普通集合转换成为固定集合。