目录
[BSON 类型](#BSON 类型)
[二进制数据(Binary Data)](#二进制数据(Binary Data))
Mongodb是文档型数据库,文档表示一条数据记录。文档由键值对构成。
//一个文档示例
{
"key_1": value_1,
"key_2": value_2
...
}
其中,键值对中的键,或叫做字段名称,是由字符类型来定义的。而值的类型,是BSON类型中的某一个类型。
BSON 类型
BSON是一个二进制序列格式,用来保存文档,实现在mongodb中的远程调用。Mongodb支持不同的BSON类型,现在可用的BSON类型,其数字代码,字符识别码列在下表中。
|--------------------|------|--------------|--------------|
| 类型 | 数字代码 | 别名 | 中文名 |
| Double | 1 | "double" | 浮点型 |
| String | 2 | "string" | 字符串 |
| Object | 3 | "object" | 对象 |
| Array | 4 | "array" | 数组 |
| Binary data | 5 | "binData" | 二进制数据 |
| ObjectId | 7 | "objectId" | 对象id |
| Boolean | 8 | "bool" | 布尔 |
| Date | 9 | "date" | 日期 |
| Null | 10 | "null" | 空值 |
| Regular Expression | 11 | "regex" | 正则表达式 |
| JavaScript | 13 | "javascript" | javascript脚本 |
| 32-bit Integer | 16 | "int" | 整型 |
| Timestamp | 17 | "timestamp" | 时间戳 |
| 64-bit Integer | 18 | "long" | 长整型 |
| Decimal128 | 19 | "decimal" | 小数 |
| Min Key | -1 | "minKey" | 最小值 |
| Max Key | 127 | "maxKey" | 最大值 |
在Mongodb中,可以使用数字代码和别名作为过滤条件查询文档。在聚合操作中,可以获取字段类型的别名。或者根据返回的字段类型实现不同的逻辑。
如查询字段类型是数组类型的数据
//使用字符串"array"和数字类型4查询字段类型是数组的数据
db.collection.find({<field_name>: {$type: "array"}})
db.collection.find({<field_name>: {$type: 4}})
二进制数据(Binary Data)
BSON的二进制数据由字节数组组成。每一个二进制数据也有一个标志解析二进制数据的子类型。下面表描述了子类型。
|-----|---------------|
| 数字 | 子类型 |
| 0 | 通用二进制数据 |
| 1 | Function data |
| 2 | Binary(old) |
| 3 | UUID(old) |
| 4 | UUID |
| 5 | MD5加密 |
| 6 | 加密BSON数值 |
| 7 | 压缩时序数据 |
| 128 | 用户自定义 |
ObjectId
使用mongodb时 , 当集合中的文档没有_id字段时,会为文档自动生成一个ObjectId类型的_id。日常查询过程中,也能看到_id是ObjectId里带有一段字符串, 这个字符串代表什么意义,和其他类型的ID有什么相同或不同。本文研究mongodb官方文档,并通过实践来解释ObjectID这个数据类型。
ObjectId定义
ObjectId是一种小型的,几乎是唯一的,易于产生和排序的数据结构。长度是12个字节,由3部分组成
- 4个字节的时间戳,表明objectId的创建时间,以秒为单位的unix时间
- 5个字节的随机字符串,在运行机器上的进程中是唯一的
- 3个字节的增长计数器,初始值是一个随机数
其中时间戳和增长计数器与其他BSON类型不同,是按照高位优先排序。
如果使用数字类型来创建ObjectID, ObjectID中的时间戳,会被数字值来替代。
//使用数字类型定义3个ObjectID
db.inventory.insertMany([{
part: 'AB307',
_id: ObjectId(1)
},{
part: 'AB307',
_id: ObjectId(2)
},{
part: 'AB307',
_id: ObjectId(3)
}])
// 产生的id中,数字类型替代了4个字节的时间戳
{
"acknowledged" : true,
"insertedId" : ObjectId("000000013e53cb5bc48f4e54")
}
{
"acknowledged" : true,
"insertedId" : ObjectId("000000023e53cb5bc48f4e55")
}
{
"acknowledged" : true,
"insertedId" : ObjectId("000000033e53cb5bc48f4e56")
}
上面代码的运行结果中可以看到,4个字节长度的00000001,00000002,00000003,替代了时间错。而后面3个字节长度的增长计数器,8f4e54,8f4e55,8f4e56在每一条新纪录插入时,都会增长。而中间3e53cb5bc4这个长度为5个字节的字符串,是由运行机器进程中随机产生的字符串。
时间戳+随机字符串+自增长字符串,保证了objectId的唯一性。
文档中的ObjectId
Mongodb的每一个文档都需要有唯一的_id字段作为主键。如果没有_id字段,mongodb自动添加一个ObjectId作为_id字段。这对于设置{upsert:true}的更新语句插入数据同样适用。
使用ObjectId作为_id字段的好处
-
在mongosh中,通过ObjectId.getTimestamp()方法,获取文档创建时间
//运行代码
ObjectId("655ef22e69185fac9ce3ce92").getTimestamp()
//获取创建时间
ISODate("2023-11-23T14:33:18.000+08:00") -
按照_id排序,等价于按照文档创建时间排序
ObjectId的单调性
ObjectId虽然按照时间来产生,并带有随机数保证其唯一性。但objectId并不是单调的,受到两个因素影响
- 产生objectId仅仅以秒为单位,记录下来unix时间。当同一秒钟产生多个objectId时,并不能保证每一个objectid的排列顺序。
- 通过client产生的ObjectId, 其时间错可能与服务器时间不一致
字符串(String)
BSON类型的字符串是UTF-8格式的。各种语言连接mongodb时,通常都会使用UTF-8格式的字符串。这种格式保证了大部分语言的通用性。
时间戳(Timestamps)
BSON 的timestamps类型是一种内部使用的特殊格式。这种类型与常见的日期(Date)类型不同。timestamps类型是一个64位的数值
-
- 前面32位的unix时间,精确到秒
- 后32位的自增型操作数。表示在一秒内的操作数。
虽然BSON格式是小端序的,因此首先存储最低有效位,但在所有平台上,mongod实例总是在序数值之前比较time_t值,而不考虑端序。
在同一个mongodb运行实例中,timestamp值是唯一的。
在复制集中,oplog有一个时间类型的ts字段。这个字段使用timestamp类型表示操作时间。
当在文档中插入一个空的timestamp数值时,mongodb会将空的timestamp数字换成当前时间。但如果_id字段包含一个空的timestamp值时,mongodb不会替换空的timestamp.
db.test.insert({ts: new Timestamp(), _id: {ts: new Timestamp()}})
{
"acknowledged" : true,
"insertedId" : {
"ts" : Timestamp(0, 0)
}
}
db.test.find()
{
"_id" : {
"ts" : Timestamp(0, 0) //_id字段中的时间戳未被替换
},
"ts" : Timestamp(1703731318, 1) //顶层字段的时间戳被替换成当前的操作时间
}
日期(Date)
日期类型是一个64位字符,表示了字1970年1月1日以来经过的毫秒数。日期时间是有符号的,负数表示1970年以前的时间。64位的字符,可以表示过去和现在共2.9亿年的时间。
var mydate1 = new Date()
var mydate2 = ISODate()
console.log(mydate1.toString())
console.log(mydate2.toString())
console.log(mydate1.getMonth())
Thu Dec 28 2023 10:48:15 GMT+0800 (China Standard Time)
Thu Dec 28 2023 10:48:15 GMT+0800 (China Standard Time)
11
BSON类型的排序
在mongodb对BSON类型进行排序时,按照下面的类型进行排序。从小到大的顺序依次为
|----|--------------------------------------|
| 序号 | 类型 |
| 1 | MinKey |
| 2 | Null |
| 3 | Numbers(ints,longs,doubles,decimals) |
| 4 | String |
| 5 | Object |
| 6 | Array |
| 7 | BinData |
| 8 | ObjectId |
| 9 | Boolean |
| 10 | Date |
| 11 | Timestamp |
| 12 | RegularExpression |
| 13 | MaxKey |
这个排序怎样理解,不妨使用下面的测试用例来解释。插入13条各种类型的数据。当使用正序排列,即从小到大排列时,顺序从MinKey到MaxKey. 当使用倒序排列时,顺序相反。
db.test.insertMany([
{value: MinKey()},
{value: null},
{value: 1},
{value: "1"},
{value: {a: 1}},
{value: [1]},
{value: new BinData(5,"1")},
{value: ObjectId(1)},
{value: true},
{value: new Date()},
{value: new Timestamp()},
{value: /a/},
{value: MaxKey()},
])
//正序排列
db.test.find().sort('value')
/* 1 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557ef"),
"value" : MinKey()
},
/* 2 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f0"),
"value" : null
},
/* 3 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f1"),
"value" : 1
},
/* 4 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f4"),
"value" : [ 1 ]
},
/* 5 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f2"),
"value" : "1"
},
/* 6 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f3"),
"value" : {
"a" : 1
}
},
/* 7 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f5"),
"value" : MD5("")
},
/* 8 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f6"),
"value" : ObjectId("000000019ef5a82e298557ee")
},
/* 9 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f7"),
"value" : true
},
/* 10 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f8"),
"value" : ISODate("2023-12-28T11:04:43.958+08:00")
},
/* 11 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f9"),
"value" : Timestamp(1703732683, 1)
},
/* 12 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557fa"),
"value" : /a/
},
/* 13 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557fb"),
"value" : MaxKey()
}
//倒序排列
/* 1 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557fb"),
"value" : MaxKey()
},
/* 2 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557fa"),
"value" : /a/
},
/* 3 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f9"),
"value" : Timestamp(1703732683, 1)
},
/* 4 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f8"),
"value" : ISODate("2023-12-28T11:04:43.958+08:00")
},
/* 5 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f7"),
"value" : true
},
/* 6 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f6"),
"value" : ObjectId("000000019ef5a82e298557ee")
},
/* 7 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f5"),
"value" : MD5("")
},
/* 8 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f3"),
"value" : {
"a" : 1
}
},
/* 9 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f2"),
"value" : "1"
},
/* 10 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f1"),
"value" : 1
},
/* 11 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f4"),
"value" : [ 1 ]
},
/* 12 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557f0"),
"value" : null
},
/* 13 createdAt:12/28/2023, 11:04:43 AM*/
{
"_id" : ObjectId("658ce5cb9ef5a82e298557ef"),
"value" : MinKey()
}
数值类型的比较
mongodb比较数字类型时,int,long,double,decimal按照同一个类型进行比较。按照值大小进行排序。
字符串比较
默认字符串按照简单字节方法比较。即从第一个字符开始进行比较。当用户指定字符序时,mongodb比较字符串时,采用用户指定的字符序。
数组的比较
- 升序排列时,获取数组中最小的元素进行比较
- 降序排列时,比较数组中最大的元素
- 比较单元素数组与非数组数据时,mongodb会直接比较该数组中的元素和非数组元素值的大小。
- 空数组排列优先级别小于null或没有该字段的文档数据。
对象的比较
- 递归比较对象中的每一个键值对
- 字段类型相同时,比较字段名
- 字段名相同时,比较字段值
- 当前字段名,字段值都相同时,按照同样的方式比较下一个字段。字段较少的对象排列优先级小于字段多的对象。
日期和时间戳
日期排在时间错前
不存在字段
mongodb将不存在的字段看做空对象。即文档{ }与{a:null}的排列顺序相同
二进制数据
- 首先比较数据长度
- 数据长度相同时,比较子类型
- 数据长度和子类型相同时,按字节比较。