简介
使用 MongoDB 时,您可以灵活地处理数据结构。您无需被锁定在维护所有文档都必须符合的特定Schema中。对于文档中的任何给定字段,您都可以使用 MongoDB 支持的任何可用数据类型。尽管这是默认的工作方式,但如果需要,您可以在 MongoDB 中强制执行 JSON Schema,以对集合添加验证。本指南不会深入探讨 Schema 设计的细节,但如果实现,它可能会对数据类型产生影响。
数据类型指定了它们接受和存储数据的通用模式。在规划数据库时,了解何时选择某种数据类型而不是另一种数据类型至关重要。所选类型将决定您如何操作数据以及如何存储数据。
JSON 和 BSON
在深入了解特定数据类型之前,了解 MongoDB 如何存储数据非常重要。MongoDB 和许多其他基于文档的 NoSQL 数据库使用 JSON(JavaScript 对象表示法)来将数据记录表示为文档。
使用 JSON 存储数据有很多优点。其中一些优点是:
- 易于阅读、学习以及在开发人员中的熟悉程度
- 格式灵活,无论是稀疏、分层还是深度嵌套
- 自描述,允许应用程序轻松操作 JSON 数据
- 允许关注最少数量的基本类型
JSON 支持所有基本数据类型,如字符串、数字、布尔值等。MongoDB 实际上将数据记录存储为二进制编码的 JSON (BSON) 文档。与 JSON 类似,BSON 支持在其他文档和数组中嵌入文档和数组。BSON 允许 JSON 中没有的额外数据类型。
MongoDB 中的数据类型有哪些?
在深入了解之前,让我们大致了解一下 MongoDB 支持的数据类型。
MongoDB 支持一系列适合各种简单和复杂数据类型的数据类型。这些包括:
文本
字符串
数值
32 位整数
64 位整数
双精度浮点数
Decimal128
日期/时间
日期
时间戳
其他
对象
数组
二进制数据
ObjectId
布尔值
空
正则表达式
JavaScript
Min Key
Max Key
在 MongoDB 中,每个 BSON 类型都有一个整数和字符串标识符。我们将在本指南中更深入地介绍其中最常见的类型。
通过 MongoDB 的文档模型,您可以将数据存储在嵌入式文档中。了解如何使用 Prisma 客户端查找、创建、更新和删除复合类型,以及通过在 Prisma Schema 中定义字段来建模数据。
字符串类型
字符串类型是最常用的 MongoDB 数据类型。JSON 中双引号 ""
内的任何值都是字符串值。任何您想存储为文本的值都最好定义为 String
类型。BSON 字符串是 UTF-8 编码的,在 MongoDB 中表示为:
Type | Number | Alias |------------------ | ------ | -------- |String | 2 | "string" |
通常,编程语言的驱动程序在序列化和反序列化 BSON 时会将语言的字符串格式转换为 UTF-8。这使得 BSON 成为一种易于存储国际字符的吸引人的方法。
插入一个包含 String
数据类型的文档将如下所示:
db.mytestcoll.insertOne({first_name: "Alex"}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d15")}
查询集合将返回以下内容:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d15"),first_name: "Alex"}
使用 $type
操作符
在继续介绍下一个数据类型之前,了解如何在进行任何插入之前检查值的类型非常重要。我们将使用之前的示例来演示如何在 MongoDB 中使用 $type
操作符。
假设我们已经有一段时间没有使用之前的 mytestcoll
集合了。我们想向集合中插入一些额外的包含 first_name
字段的文档。为了检查我们最初是否将 String
用作 first_name
的值的数据类型,我们可以使用数据类型的别名或数值运行以下命令:
db.mytestcoll.find( { "first_name": { $type: "string" } } )
或者
db.mytestcoll.find( { "first_name": { $type: 2 } } )
这两个查询都会返回所有在 first_name
字段中存储了 String
值的文档,这些文档是我们上一节插入的:
[ { _id: ObjectId("614b37296a124db40ae74d15"), first_name: "Alex" } ]
如果您查询的类型未存储在任何文档的 first_name
字段中,则不会得到任何结果。这表明 first_name
中存储的是另一种数据类型。
您还可以使用 $type
操作符同时查询多种数据类型,如下所示:
db.mytestcoll.find( { "first_name": { $type: ["string", "null"] } } )
因为我们没有在集合中插入任何 Null
类型的值,所以结果将相同:
[ { _id: ObjectId("614b37296a124db40ae74d15"), first_name: "Alex" } ]
您可以使用相同的方法处理我们接下来讨论的所有类型。
数字和数值
MongoDB 包含一系列适合不同场景的数值数据类型。决定使用哪种类型取决于您计划存储的值的性质以及您对数据的使用案例。JSON 将所有数字都称为Number。这使得系统必须想办法将其转换为最接近的本机数据类型。我们将首先探索整数以及它们在 MongoDB 中的工作方式。
整数
Integer
数据类型用于存储整数,不包含任何分数或小数。整数可以是正值或负值。MongoDB 中有两种类型,32 位整数
和 64 位整数
。它们可以通过下表所示的两种方式表示,number
和 alias
:
Integer type | number | alias |------------ | ----- | ------------ |`32-bit integer`| 16 | "int" |`64-bit integer`| 18 | "long" |
每种类型的值可容纳的范围如下:
Integer type | Applicable signed range | Applicable unsigned range |------------ | ------------------------------ | ------------------------------- |`32-bit integer`| -2,147,483,648 to 2,147,483,647| 0 to 4,294,967,295 |`64-bit integer`| -9,223,372,036,854,775,808 to | 0 to 18,446,744,073,709,551,6159,223,372,036,854,775,807
上述类型受其有效范围限制。超出范围的任何值都将导致错误。在 MongoDB 中插入 Integer
类型将如下所示:
db.mytestcoll.insertOne({age: 26}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d14")}
查找结果将返回以下内容:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d14"), age: 26}
顾名思义,32 位整数
具有 32 位的整数精度,这对于您不想以数字序列形式存储的较小整数值很有用。当数字大小增长时,您可以升级到 64 位整数
,它具有 64 位的整数精度,并适用于与前者相同的用例。
双精度浮点数
在 BSON 中,JSON 的 Number 的默认替代是 Double
数据类型。Double
数据类型用于存储浮点值,在 MongoDB 中可以表示为:
Type | Number | Alias |------------------ | ------ | -------- |Double | 1 | "double" |
浮点数是表示小数的另一种方式,但没有精确、一致的精度。
浮点数可以高效地处理大量小数,但并不总是精确。以下是将 Double
类型文档输入到集合中的示例:
db.mytestcoll.insertOne({testScore: 89.6}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d13")}
使用双精度浮点数进行计算时,输入和输出之间可能存在细微差异,这可能会导致意外行为。当执行需要精确值的操作时,MongoDB 有更精确的类型。
Decimal128
如果您正在处理带有大量浮点范围的非常大的数字,那么 Decimal128
BSON 数据类型将是最佳选择。这对于需要高精度值的场景(例如涉及精确货币操作的用例)最有用。Decimal128
类型表示为:
Type | Number | Alias |------------------ | ------ | --------- |Decimal128 | 19 | "decimal" |
BSON 类型 Decimal128
提供 128 位的十进制表示,用于存储舍入小数很重要(例如需要精确的舍入)的数字。Decimal128
支持 34 位十进制精度,或者有效数字范围为 -6143 到 +6144。有效数字 范围为 -6143 到 +6144。这允许高精度。
使用 Decimal128
数据类型插入值需要使用 NumberDecimal()
构造函数,并将您的数字作为 String
传递,以防止 MongoDB 使用默认的数字类型 Double
。
这里我们演示一下:
db.mytestcoll.insertOne({price : NumberDecimal("5.099")}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d12")}
查询集合时,您将得到以下返回结果:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d12"),price: "5.099"}
数值保持其精度,允许精确操作。为了演示 Decimal128
类型与 Double
的区别,我们可以进行以下练习。
基于数据类型如何丢失精度
假设我们想将一个带有许多小数的数字作为 Double
插入到 MongoDB 中,如下所示:
db.mytestcoll.insertOne({ price: 9999999.4999999999 }){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d24")}
当我们查询这些数据时,得到以下结果:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d24"),price: 9999999.5}
此值四舍五入到 9999999.5
,丢失了我们输入时的精确值。这使得 Double
不适合存储具有许多小数的数字。
下一个示例演示了当隐式使用 Decimal128
传递 Double
而不是像上一个示例中那样使用 String
时,精度将如何丢失。
我们首先再次插入以下 Double
,但这次使用 NumberDecimal()
将其转换为 Decimal128
类型:
db.mytestcoll.insertOne({ price: NumberDecimal( 9999999.4999999999 ) }){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d14")}
注意:在 MongoDB shell 中进行此插入时,会显示以下警告消息:
Warning: NumberDecimal: specifying a number as argument is deprecated and may lead toloss of precision, pass a string instead
此警告消息表示您尝试传递的数字可能会丢失精度。它们建议使用 NumberDecimal()
和 String
,这样您就不会丢失任何精度。
如果我们忽略警告并仍然插入文档,则从值的四舍五入中可以看出查询结果中精度的丢失:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d14"),price: Decimal128("9999999.50000000")}
如果我们遵循推荐的 NumberDecimal()
方法,使用 String
,我们将看到以下结果,精度得以保持:
db.mytestcoll.insertOne({ price: NumberDecimal( "9999999.4999999999" ) } )
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d14"),price: Decimal128("9999999.4999999999")}
对于任何需要精确值的用例,这种返回可能会导致问题。任何涉及货币操作的工作都是一个例子,其中精度将极其重要,并且具有精确值对于准确计算至关重要。此演示强调了了解哪种数字数据类型最适合您的数据的重要性。
日期
BSON Date
数据类型是一个 64 位整数,表示自 Unix 纪元(1970 年 1 月 1 日)以来的毫秒数。此数据类型存储当前日期或时间,可以作为日期对象或字符串返回。Date
在 MongoDB 中的表示如下:
Type | Number | Alias |------------------ | ------ | ------------ |Date | 9 | "date" |
注意:BSON Date
类型是有符号的。负值表示 1970 年之前的日期。
有三种方法可以返回日期值。
Date()
- 返回一个字符串new Date()
- 使用ISODate()
包装器返回一个日期对象ISODate()
- 也使用ISODate()
包装器返回一个日期对象
我们将在下面演示这些选项:
var date1 = Date()var date2 = new Date()var date3 = ISODate()db.mytestcoll.insertOne({firstDate: date1, secondDate: date2, thirdDate: date3}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d22")}
返回时:
db.mytestcoll.find().pretty(){"_id" : ObjectId("614b37296a124db40ae74d22"),firstDate: 'Tue Sep 28 2021 11:28:52 GMT+0200 (Central European Summer Time)',secondDate: ISODate("2021-09-28T09:29:01.924Z"),thirdDate: ISODate("2021-09-28T09:29:12.151Z")}
时间戳
MongoDB 中还有 Timestamp
数据类型,用于表示时间。然而,Timestamp
最常用于内部使用,并且不与 Date
类型相关联。该类型本身是用于描述事件发生日期和时间的一系列字符。Timestamp
是一个 64 位值,其中:
- 最高有效 32 位是
time_t
值(自 Unix 纪元以来的秒数) - 最低有效 32 位是给定秒内操作的递增
ordinal
它在 MongoDB 中的表示将如下所示:
Type | Number | Alias |------------------ | ------ | ------------ |Timestamp | 17 | "timestamp" |
当插入包含顶级字段(时间戳为空)的文档时,MongoDB 将用当前时间戳值替换空时间戳值。例外情况是如果 _id
字段包含空时间戳。时间戳值将始终按原样插入,不会被替换。
在 MongoDB 中插入新的 Timestamp
值将使用 new Timestamp()
函数,看起来像这样:
db.mytestcoll.insertOne( {ts: new Timestamp() });{"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d23")}
查询集合时,您将返回类似以下结果:
db.mytestcoll.find().pretty(){"_id" : ObjectId("614b37296a124db40ae74d24"),"ts" : Timestamp( { t: 1412180887, i: 1 })}
对象
MongoDB 中的 Object
数据类型用于存储嵌入式文档。嵌入式文档是一系列嵌套文档,以 key: value
对格式存在。我们在下面演示 Object
类型:
var classGrades = {"Physics": 88, "German": 92, "LitTheoery": 79}db.mytestcoll.insertOne({student_name: "John Smith", report_card: classGrades}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d18")}
然后我们可以查看我们的新文档:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d18"),student_name: 'John Smith',report_card: {Physics: 88, German: 92, LitTheoery: 79}}
Object
数据类型优化了同时访问的数据的存储。与分别存储每个班级分数(如上述示例)相比,它在存储、速度和持久性方面提供了一些效率。
二进制数据
Binary data
或 BinData
数据类型正如其名称所示,存储字段值的二进制数据。BinData
最适合在存储和搜索数据时使用,因为它在表示位数组方面效率很高。此数据类型可以通过以下方式表示:
Type | Number | Alias |------------------ | ------ | ------------ |Binary data | 5 | "binData" |
以下是向集合中的文档添加一些 Binary data
的示例:
var data = BinData(1, "111010110111100110100010101")db.mytestcoll.insertOne({binaryData: data}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d20")}
然后查看结果文档:
db.mytestcoll.find().pretty(){"_id" : ObjectId("614b37296a124db40ae74d20"),"binaryData" : BinData(1, "111010110111100110100010101")}
ObjectId
ObjectId
类型是 MongoDB 特有的,它存储文档的唯一 ID。MongoDB 为每个文档提供一个 _id
字段。ObjectId 的大小为 12 字节,可以表示为:
Type | Number | Alias |------------------ | ------ | ------------ |ObjectId | 7 | "objectId" |
ObjectId 由三部分组成,构成其 12 字节的结构:
- 一个 4 字节的时间戳值,表示 ObjectId 的创建时间,以自 Unix 纪元以来的秒数衡量
- 一个 5 字节的随机值
- 一个 3 字节的递增计数器,初始化为随机值
在 MongoDB 中,集合中的每个文档都需要一个唯一的 _id
作为主键。如果插入文档时 _id
字段留空,MongoDB 将自动为该字段生成一个 ObjectId。
使用 ObjectId 作为 _id
有几个好处:
- 在
mongosh
(MongoDB shell) 中,可以使用ObjectId.getTimestamp()
方法访问ObjectId
的创建时间。 - 对存储
ObjectId
数据类型的_id
字段进行排序与按创建时间排序的效果接近。
我们已经在本例中看到了 ObjectIds,它们看起来会与此类似:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d19")}
注意:ObjectId 值应随时间增加,但它们不一定是单调的。这是因为它们:
- 只包含一秒的时间分辨率,因此在同一秒内创建的值不保证有序
- 值由客户端生成,客户端可能有不同的系统时钟
布尔值
MongoDB 具有原生 Boolean
数据类型,用于在集合中存储 true 和 false 值。Boolean
在 MongoDB 中可以表示为:
Type | Number | Alias |------------------ | ------ | ------------ |Boolean | 8 | "bool" |
插入一个带有 Boolean
数据类型的文档将如下所示:
db.mytestcoll.insertOne({isCorrect: true, isIncorrect: false}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d21")}
然后,在搜索文档时,结果将显示为:
db.mytestcoll.find().pretty(){"_id" : ObjectId("614b37296a124db40ae74d21")"isCorrect" : true,"isIncorrect" : false}
正则表达式
MongoDB 中的 Regular Expression
数据类型允许将正则表达式作为字段的值存储。MongoDB 使用 PCRE(Perl 兼容正则表达式)作为其正则表达式语言。
它可以表示为:
Type | Number | Alias |------------------ | ------ | ------- |Regular Expression | 11 | "regex" |
BSON 允许您避免在使用正则表达式和数据库时常见的“从字符串转换”步骤。当您编写需要验证模式或匹配触发器的数据库对象时,此类型最有用。
例如,您可以像这样插入 Regular Expression
数据类型:
db.mytestcoll.insertOne({exampleregex: /tt/}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d16")}db.mytestcoll.insertOne({exampleregext:/t+/}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d17")}
这一系列语句将这些文档添加到您的集合中。然后,您可以查询您的集合以查找插入的文档:
db.mytestcoll.find().pretty(){_id: ObjectId("614b37296a124db40ae74d16"), exampleregex: /tt/,_id: ObjectId("614b37296a124db40ae74d17"), exampleregex: /t+/}
正则表达式模式以正则表达式而非字符串的形式存储。这允许您查询特定字符串,并返回具有与所需字符串匹配的正则表达式的文档。
JavaScript(无作用域)
与前面提到的 Regular Expression
数据类型非常相似,BSON 允许 MongoDB 将不带作用域的 JavaScript 函数存储为它们自己的类型。JavaScript
类型可以识别为:
Type | Number | Alias |------------------ | ------ | ------------ |JavaScript | 13 | "javascript" |
向集合中添加一个带有 JavaScript
数据类型的文档将如下所示:
db.mytestcoll.insertOne({jsCode: "function(){var x; x=1}"}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d122")}
此功能允许您在需要特定用例时将 JavaScript 函数存储在 MongoDB 集合中。
注意:从 MongoDB 4.4 版本及更高版本起,另一种 JavaScript 类型,即带作用域的 JavaScript with Scope
数据类型已被弃用。
总结
在本文中,我们介绍了在处理 MongoDB 数据库时有用的大多数常见数据类型。本指南中未明确涵盖其他类型,这些类型可能根据用例而有所帮助。了解这些类型可以满足大多数用例,是开始建模 MongoDB 数据库的坚实基础。
了解数据库中可用的数据类型非常重要,这样您才能使用有效值并以预期结果操作数据。如果没有正确地对数据进行类型化,您可能会遇到风险,正如在 Double
与 Decimal128
练习中所示。在确定任何给定类型之前,提前考虑这一点很重要。
如果您有兴趣使用 MongoDB 数据库查看 Prisma,您可以查看数据连接器文档。
如果您正在使用 MongoDB,请查看 Prisma 的 MongoDB 连接器!您可以使用 Prisma Client 信心十足地管理生产环境 MongoDB 数据库。
要开始使用 MongoDB 和 Prisma,请查看我们的从零开始指南或如何添加到现有项目。
常见问题
可以,MongoDB 允许使用 Regular Expression
数据类型存储正则表达式。
例如,您可以像这样插入 Regular Expression
数据类型:
db.mytestcoll.insertOne({exampleregex: /tt/}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d16")}db.mytestcoll.insertOne({exampleregext:/t+/}){"acknowledged": true,"insertedId": ObjectId("614b37296a124db40ae74d17")}
是的,MongoDB 可以存储带有 BSON 数组类型的数组。它在 MongoDB 中表示如下:
Type | Number | Alias |------------------ | ------ | ------------ |Array | 4 | "array" |