分享到

简介

当使用 MongoDB 时,您可以灵活地处理数据结构。您不会被锁定在维护特定的 模式 中,所有 文档 都必须符合该模式。对于文档中的任何给定字段,您都可以使用 MongoDB 支持的任何可用的 数据类型。尽管默认的工作方式如此,但如果需要,您可以在 MongoDB 中强制使用 JSON 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
  • 最小值键
  • 最大值键

在 MongoDB 中,每个 BSON 类型都有整数和字符串标识符。我们将在本指南的后续部分中更深入地介绍其中最常见的类型。

字符串类型

字符串类型是最常用的 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 将任何带数字的内容都称为数字。这迫使系统弄清楚如何将其转换为最接近的本机数据类型。我们将从探索整数以及它们在 MongoDB 中的工作方式开始。

整数

Integer 数据类型用于将数字存储为不带任何分数或小数的整数。整数可以是正值或负值。MongoDB 中有两种类型,32 位整数64 位整数。它们可以用下表所示的两种方式表示,numberalias

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,615
9,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 的数字的默认替换是 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。这允许高精度。

使用 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 不适合存储具有许多小数的数字。

下一个示例演示了当隐式传递 Double 并使用 Decimal128 而不是像上一个示例中那样使用 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 to
loss of precision, pass a string instead

此警告消息表明您尝试传递的数字可能会丢失精度。他们建议使用 StringNumberDecimal(),这样您就不会丢失任何精度。

如果我们忽略警告并仍然插入文档,则从值的向上舍入中可以看出查询结果中精度的损失

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 年之前的日期。

有三种返回日期值的方法。

  1. Date() - 返回字符串

  2. new Date() - 使用 ISODate() 包装器返回日期对象

  3. 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 dataBinData 数据类型完全按照其名称的含义执行,并存储字段值的二进制数据。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。

_id 使用 ObjectId 有几个好处

  • mongosh (MongoDB shell) 中,可以使用 ObjectId.getTimestamp() 方法访问 ObjectId 的创建时间。
  • 对存储 ObjectId 数据类型的 _id 字段进行排序,与按创建时间排序几乎相同。

到目前为止,我们在示例中看到了 ObjectId,它们看起来与此类似

db.mytestcoll.find().pretty()
{
_id: ObjectId("614b37296a124db40ae74d19")
}

注意:ObjectId 值应随时间增加,但它们不一定是单调的。这是因为它们

  • 仅包含一秒的时间分辨率,因此在同一秒内创建的值不保证排序
  • 值由客户端生成,客户端可能具有不同的系统时钟

布尔值

MongoDB 具有本机 Boolean 数据类型,用于在集合中存储 true 和 false 值。MongoDB 中的 Boolean 可以表示如下

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+/
}

正则表达式模式存储为 regex,而不是字符串。这允许您查询特定字符串,并返回具有与所需字符串匹配的正则表达式的文档。

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 数据库的坚实基础。

了解在使用数据库时可用的数据类型非常重要,这样您才能使用有效的值并对数据进行预期结果的操作。如果您没有正确键入数据,可能会遇到风险,如 DoubleDecimal128 练习中所示。在提交任何给定类型之前,预先考虑这一点非常重要。

如果您有兴趣使用 Prisma 和 MongoDB 数据库,您可以查看数据连接器文档

常见问题解答

是的,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")
}

BSON 日期是一个 64 位整数,表示自 Unix 纪元以来的毫秒数。

有三种返回日期值的方法。

Date() - 返回字符串

new Date() - 使用 ISODate() 包装器返回日期对象

ISODate() - 也使用 ISODate() 包装器返回日期对象

返回数据的显示方式取决于其存储方法。

是的,MongoDB 为事务数据同时支持单文档结构和多文档结构。

还提供 API 和其他 操作 以用于事务数据。

对于小于 16MB 的图像,MongoDB 可以使用 BinData 数据类型存储图像。

对于大于 16MB 的图像,MongoDB 建议使用 GridFS 存储在您的数据库中。

是的,MongoDB 可以使用 BSON 数组类型存储数组。它在 MongoDB 中表示如下

Type | Number | Alias |
------------------ | ------ | ------------ |
Array | 4 | "array" |
关于作者
Alex Emerich

Alex Emerich

Alex 是典型的观鸟爱好者、嘻哈音乐迷和书虫,他也喜欢撰写关于数据库的文章。他目前居住在柏林,在那里人们可以看到他像利奥波德·布鲁姆一样漫无目的地在城市中漫步。