JSON 是当今非常通用的一种跨语言 Web 数据交互格式,属 ECMAScript 标准规范的一个子集。JSON (JavaScript Object Notation,JS 对象简谱)即 JavaScript 对象表示法,它是 JavaScript 对象的一种文本表现形式。
作为一种轻量级的数据交换格式,JSON 的可读性非常好,而且非常便于系统生成和解析,这些优势也让它逐渐取代了 XML 标准在 Web 领域的地位,当今许多流行的 Web 应用开发框架,如 SpringBoot 都选择了 JSON 作为默认的数据编/解码格式。
JSON 只定义了 6 种数据类型:
大多数情况下,使用 JSON 作为数据交互格式已经是理想的选择,但是 JSON 基于文本的解析效率并不是最好的,在某些场景下往往会考虑选择更合适的编/解码格式,一些做法如:
BSON 由 10gen 团队设计并开源,目前主要用于 MongoDB 数据库。BSON(Binary JSON)是二进制版本的 JSON,其在性能方面有更优的表现。BSON 在许多方面和 JSON 保持一致,其同样也支持内嵌的文档对象和数组结构。二者最大的区别在于 JSON 是基于文本的,而 BSON 则是二进制(字节流)编/解码的形式。在空间的使用上,BSON 相比 JSON 并没有明显的优势。
MongoDB 在文档存储、命令协议上都采用了 BSON 作为编/解码格式,主要具有如下优势:
MongoDB 中,一个 BSON 文档最大大小为 16M,文档嵌套的级别不超过100。
https://www.mongodb.com/docs/v6.0/reference/bson-types/
Type | Number | Alias | Notes |
---|---|---|---|
Double | 1 | “double” | |
String | 2 | “string” | |
Object | 3 | “object” | |
Array | 4 | “array” | |
Binary data | 5 | “binData” | 二进制数据 |
Undefined | 6 | “undefined” | Deprecated. |
ObjectId | 7 | “objectId” | 对象ID,用于创建文档ID |
Boolean | 8 | “bool” | |
Date | 9 | “date” | |
Null | 10 | “null” | |
Regular Expression | 11 | “regex” | 正则表达式 |
DBPointer | 12 | “dbPointer” | Deprecated. |
JavaScript | 13 | “javascript” | |
Symbol | 14 | “symbol” | Deprecated. |
JavaScript code with scope | 15 | “javascriptWithScope” | Deprecated in MongoDB 4.4. |
32-bit integer | 16 | “int” | |
Timestamp | 17 | “timestamp” | |
64-bit integer | 18 | “long” | |
Decimal128 | 19 | “decimal” | New in version 3.4. |
Min key | -1 | “minKey” | 表示一个最小值 |
Max key | 127 | “maxKey” | 表示一个最大值 |
t y p e 操作符: < b r / > type 操作符:
type操作符:
type 操作符基于 BSON 类型来检索集合中匹配的数据类型,并返回结果。
db.books.find({"title" : {$type : 2}}) // 或者 db.books.find({"title" : {$type : "string"}})
MongoDB 的日期类型使用 UTC(Coordinated Universal Time,即世界协调时)进行存储,也就是 +0 时区的时间。
db.dates.insertMany([{data1:Date()},{data2:new Date()},{data3:ISODate()}]) db.dates.find().pretty()
使用 new Date 与 ISODate 最终都会生成 ISODate 类型的字段(对应于 UTC 时间)。
MongoDB 集合中所有的文档都有一个唯一的 _id 字段,作为集合的主键。在默认情况下,_id 字段使用 ObjectId 类型,采用 16 进制编码形式,共 12 个字节。
为了避免文档的 _id 字段出现重复,ObjectId 被定义为 3 个部分:
大多数客户端驱动都会自行生成这个字段,比如 MongoDB Java Driver 会根据插入的文档是否包含 _id 字段来自动补充 ObjectId 对象。这样做不但提高了离散性,还可以降低 MongoDB 服务器端的计算压力。在 ObjectId 的组成中,5 字节的随机数并没有明确定义,客户端可以采用机器号、进程号来实现:
属性/方法 | 描述 |
---|---|
str | 返回对象的十六进制字符串表示。 |
ObjectId.getTimestamp() | 将对象的时间戳部分作为日期返回。 |
ObjectId.toString() | 以字符串文字“”的形式返回 JavaScript 表示ObjectId(…)。 |
ObjectId.valueOf() | 将对象的表示形式返回为十六进制字符串。返回的字符串是 str 属性。 |
生成一个新的 ObjectId:
x = ObjectId()
一个文档中可以包含作者的信息,包括作者名称、性别、家乡所在地,一个显著的优点是,当我们查询 book 文档的信息时,作者的信息也会一并返回。
db.books.insert({ title: "撒哈拉的故事", author: { name:"三毛", gender:"女", hometown:"重庆" } })
查询三毛的作品:
db.books.find({"author.name":"三毛"})
修改三毛的家乡所在地:
db.books.updateOne({"author.name":"三毛"},{$set:{"author.hometown":"重庆/台湾"}})
除了作者信息,文档中还包含了若干个标签,这些标签可以用来表示文档所包含的一些特征,如豆瓣读书中的标签(tag)。
增加 tags 标签:
db.books.updateOne({"author.name":"三毛"},{$set:{tags:["旅行","随笔","散文","爱情","文学"]}})
查询数组元素:
# 会查询到所有的tags db.books.find({"author.name":"三毛"},{title:1,tags:1}) # 利用$slice获取最后一个tag db.books.find({"author.name":"三毛"},{title:1,tags:{$slice:-1}})
$silice 是一个查询操作符,用于指定数组的切片方式
数组末尾追加元素,可以使用 $push 操作符:
db.books.updateOne({"author.name":"三毛"},{$push:{tags:"猎奇"}})
$push 操作符可以配合其他操作符,一起实现不同的数组修改操作,比如和 $each 操作符配合可以用于添加多个元素:
db.books.updateOne({"author.name":"三毛"},{$push:{tags:{$each:["伤感","想象力"]}}})
如果加上 $slice 操作符,那么只会保留经过切片后的元素:
db.books.updateOne({"author.name":"三毛"},{$push:{tags:{$each:["伤感","想象力"],$slice:-3}}})
根据元素查询:
# 会查出所有包含伤感的文档 db.books.find({tags:"伤感"}) # 会查出所有同时包含"伤感","想象力"的文档 db.books.find({tags:{$all:["伤感","想象力"]}})
数组元素可以是基本类型,也可以是内嵌的文档结构
{ tags:[ {tagKey:xxx,tagValue:xxxx}, {tagKey:xxx,tagValue:xxxx} ] }
这种结构非常灵活,一个很适合的场景就是商品的多属性表示。
一个商品可以同时包含多个维度的属性,比如尺码、颜色、风格等,使用文档可以表示为:
db.goods.insertMany([{ name:"羽绒服", tags:[ {tagKey:"size",tagValue:["M","L","XL","XXL","XXXL"]}, {tagKey:"color",tagValue:["黑色","宝蓝"]}, {tagKey:"style",tagValue:"韩风"} ] },{ name:"羊毛衫", tags:[ {tagKey:"size",tagValue:["L","XL","XXL"]}, {tagKey:"color",tagValue:["蓝色","杏色"]}, {tagKey:"style",tagValue:"韩风"} ] }])
以上的设计是一种常见的多值属性的做法,当我们需要根据属性进行检索时,需要用到 $elementMatch 操作符:
# 筛选出color=黑色的商品信息 db.goods.find({ tags:{ $elemMatch:{tagKey:"color",tagValue:"黑色"} } })
如果进行组合式的条件检索,则可以使用多个 $elemMatch 操作符:
# 筛选出color=蓝色,并且size=XL的商品信息 db.goods.find({ tags:{ $all:[ {$elemMatch:{tagKey:"color",tagValue:"黑色"}}, {$elemMatch:{tagKey:"size",tagValue:"XL"}} ] } })
https://www.mongodb.com/docs/manual/core/capped-collections/
固定集合(capped collection)是一种限定大小的集合,其中 capped 是覆盖、限额的意思。跟普通的集合相比,数据在写入这种集合时遵循 FIFO 原则。可以将这种集合想象为一个环状的队列,新文档在写入时会被插入队列的末尾,如果队列已满,那么之前的文档就会被新写入的文档所覆盖。通过固定集合的大小,我们可以保证数据库只会存储“限额”的数据,超过该限额的旧数据都会被丢弃。
创建固定集合:
db.createCollection("logs",{capped:true,size:4096,max:10})
max:指集合的文档数量最大值,这里是 10 条
size:指集合的空间占用最大值,这里是 4096 字节(4 KB)
这两个参数会同时对集合的上限产生影响。也就是说,只要任一条件达到阈值都会认为集合已经写满。其中 size 是必选的,而 max 则是可选的。
可以使用collection.stats命令查看文档的占用空间:
db.logs.stats()
将普通集合转换为固定集合:
db.runCommand({"convertToCapped": "mycoll", size: 100000})
测试:
尝试在这个集合中插入 15 条数据,再查询会发现,由于文档数量上限被设定为 10 条,前面插入的 5 条数据已经被覆盖了。
for(var i=0;i<15;i++){ db.logs.insert({t:"row-"+i}) }
固定集合很适合用来存储一些“临时态”的数据。“临时态”意味着数据在一定程度上可以被丢弃。同时,用户还应该更关注最新的数据,随着时间的推移,数据的重要性逐渐降低,直至被淘汰处理。
一些适用的场景如下:
这非常符合固定集合的特征,而日志系统通常也只需要一个固定的空间来存放日志。在 MongoDB 内部,副本集的同步日志(oplog)就使用了固定集合。
如最新发布的 TopN 条文章信息。得益于内部缓存的作用,对于这种少量文档的查询是非常高效的。