功能差异: Amazon DocumentDB 和 MongoDB - Amazon DocumentDB
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

功能差异: Amazon DocumentDB 和 MongoDB

以下是 Amazon DocumentDB(与 MongoDB 兼容) 与 之间的功能差异MongoDB。

的功能优势Amazon DocumentDB

隐式事务

在 Amazon DocumentDB 中,所有 CRUD 语句(findAndModifyupdateinsertdelete)均保证原子性和一致性,即使对于修改多个文档的操作也是如此。启动 Amazon DocumentDB 4.0 后,现在支持为多语句和多集合操作提供 ACID 属性的显式事务。有关在 中使用事务的更多信息Amazon DocumentDB,请参阅Transactions

下面示例中介绍的 Amazon DocumentDB 中的操作用于修改可同时满足原子行为和一致行为的多个文档。

db.miles.update( { "credit_card": { $eq: true } }, { $mul: { "flight_miles.$[]": NumberInt(2) } }, { multi: true } )
db.miles.updateMany( { "credit_card": { $eq: true } }, { $mul: { "flight_miles.$[]": NumberInt(2) } } )
db.runCommand({ update: "miles", updates: [ { q: { "credit_card": { $eq: true } }, u: { $mul: { "flight_miles.$[]": NumberInt(2) } }, multi: true } ] })
db.products.deleteMany({ "cost": { $gt: 30.00 } })
db.runCommand({ delete: "products", deletes: [{ q: { "cost": { $gt: 30.00 } }, limit: 0 }] })

组成批量操作(如 updateManydeleteMany)的各个操作是原子操作,但整个批量操作不是原子操作。例如,如果各个插入操作成功执行而未出现错误,则整个 insertMany 操作是原子操作。如果 insertMany 操作遇到错误,insertMany 操作中每个单独的插入语句都将作为原子操作执行。如果您需要 insertManyupdateManydeleteMany 操作的 ACID 属性,建议使用 事务。

更新的功能差异

Amazon DocumentDB MongoDB 通过从客户要求我们构建的功能中向后工作, 可不断提高与 的兼容性。本部分包含我们已在 Amazon DocumentDB 中删除的功能差异,以便客户能够更轻松地迁移和构建应用程序。

数组索引

自 2020 年 4 月 23 日起,Amazon DocumentDB 现在支持对大于 2,048 个字节的数组编制索引的功能。数组中单个项目的限制仍保持为 2,048 字节,这与 一致MongoDB。

如果您正在创建新索引,则无需操作即可利用改进的功能。如果您有现有索引,则可以通过删除索引然后重新创建索引来利用改进的功能。具有改进功能的当前索引版本为 "v" : 3.

注意

对于生产集群,删除索引可能会影响您的应用程序性能。我们建议您在更改生产系统时首先进行测试并谨慎行事。此外,重新创建索引所需的时间将是集合的总体数据大小的函数。

您可以使用以下命令查询索引的版本。

db.collection.getIndexes()

此操作的输出将类似于下文。在此输出中,索引的版本是 "v" : 3,这是最新的索引版本。

[ { "v" : 3, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.test" } ]

多键索引

自 2020 年 4 月 23 日起,Amazon DocumentDB 现在支持在同一个数组中创建具有多个键的复合索引的功能。

如果您正在创建新索引,则无需操作即可利用改进的功能。如果您有现有索引,则可以通过删除索引然后重新创建索引来利用改进的功能。具有改进功能的当前索引版本为 "v" : 3.

注意

对于生产集群,删除索引可能会影响您的应用程序性能。我们建议您在更改生产系统时首先进行测试并谨慎行事。此外,重新创建索引所需的时间将是集合的总体数据大小的函数。

您可以使用以下命令查询索引的版本。

db.collection.getIndexes()

此操作的输出将类似于下文。在此输出中,索引的版本是 "v" : 3,这是最新的索引版本。

[ { "v" : 3, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.test" } ]

字符串中的 Null 字符

自 2020 年 6 月 22 日起,Amazon DocumentDB 现在支持字符串中的 null 字符 ('\0'

基于角色的访问控制

自 2020 年 3 月 26 日起,Amazon DocumentDB 面向内置角色支持基于角色的访问控制 (RBAC)。要了解更多信息,请参阅基于角色的访问控制。Amazon DocumentDB 尚不支持 RBAC 的自定义角色。

$regex 索引

自 2020 年 6 月 22 日起, Amazon DocumentDB 现在支持 $regex 运算符使用索引的功能。

要将索引与 $regex 运算符一起使用,您必须使用 hint() 命令。使用 hint() 时,您必须指定将 $regex 应用到的字段的名称。例如,如果您在字段 product 上有索引,且索引名称为 p_1,则 db.foo.find({product: /^x.*/}).hint({product:1}) 将使用 p_1 索引,但 db.foo.find({product: /^x.*/}).hint(“p_1”) 不使用该索引。您可以通过使用 explain() 命令或使用分析器记录慢速查询来验证是否选择了索引。例如,db.foo.find({product: /^x.*/}).hint(“p_1”).explain()

注意

hint() 方法一次只能与一个索引一起使用。

$regex 查询使用索引的功能,针对使用前缀但未指定 Imo 正则表达式选项的正则表达式查询进行了优化。

将索引与 $regex 结合使用时,建议您在具有高选择性的字段上创建索引,这些字段的重复值数量低于集合中总文档数的 1% 的字段。例如,如果您的集合包含 100,000 个文档,则仅在相同值出现 1000 次或更少的字段上创建索引。

嵌套文档的投影

与 版本 3.6 $project Amazon DocumentDB 中的MongoDB运算符存在功能差异,该运算符已在 Amazon DocumentDB 4.0 中解析,但在 Amazon DocumentDB 3.6 中仍然不受支持。

Amazon DocumentDB 3.6 在应用投影时仅考虑嵌套文档中的第一个字段,而 MongoDB 3.6 将解析子文档并将投影应用于每个子文档。

例如:如果投影为 “a.b.c”: 1,则行为在 Amazon DocumentDB 和 中均可按预期运行MongoDB。 但是,如果投影为 {a:{b:{c:1}}} 3.6Amazon DocumentDB,则 仅将投影应用于 a ,而不应用于 bc。 在 Amazon DocumentDB 4.0 中,投影{a:{b:{c:1}}}将应用于 abc

的功能差异 MongoDB

管理数据库和集合

Amazon DocumentDB 不支持管理员或本地数据库,也不支持 MongoDB system.*startup_log 集合。

cursormaxTimeMS

在 中Amazon DocumentDB, 将cursor.maxTimeMS重置每个getMore请求的计数器。因此,如果指定maxTimeMS了 3000MS查询采用 2800MS每个后续getMore请求采用 300MS则游标不会超时。仅当单个操作(查询或单个getMore请求)的运行时间超过指定的 时,游标才会超时maxTimeMS。 此外,检查光标执行时间的扫描器按一分钟的粒度运行。

explain()

Amazon DocumentDB 在利用分布式、容错、自我修复的存储系统的专用数据库引擎上模拟 MongoDB 4.0 API。因此,查询计划和 的输出在 explain() 和 之间Amazon DocumentDB可能不同MongoDB。 希望控制其查询计划的客户可以使用 $hint 运算符强制选择首选索引。

字段名称限制

Amazon DocumentDB 不支持句点“”。文档字段名称中的 ,例如 db.foo.insert({‘x.1’:1})

Amazon DocumentDB 也不支持字段名称中的 $ 前缀。

例如,在 Amazon DocumentDB 或 中尝试以下命令MongoDB:

rs0:PRIMARY< db.foo.insert({"a":{"$a":1}})

MongoDB 将返回以下内容:

WriteResult({ "nInserted" : 1 })

Amazon DocumentDB 将返回错误:

WriteResult({ "nInserted" : 0, "writeError" : { "code" : 2, "errmsg" : "Document can't have $ prefix field names: $a" } })
注意

此功能差异有一个例外情况。以下以 $ 前缀开头的字段名称已列入白名单,可以在 Amazon 中成功使用DocumentDB:$id、$ref 和 $db。

索引构建

Amazon DocumentDB在任何给定时间, 只允许在一个集合中构建一个索引(前台或后台)。如果当前正在构建索引,在同一集合上发生了 createIndex()dropIndex() 之类的操作,则新尝试的操作将失败。

在索引构建(前台或后台)完成后,有效时间 (TTL) 索引开始使文档过期。

在路径中查找空键

当您查找的键包含空字符串作为路径的一部分(例如 x.x..b),并且对象在数组内具有空字符串键路径(例如 {"x" : [ { "" : 10 }, { "b" : 20 } ]}), Amazon DocumentDB 会返回与您在 中运行相同查找时返回的结果不同的结果MongoDB。

在 中MongoDB,当空字符串键未位于路径查找的末尾时,空键路径在数组中的查找按预期工作。但是,当空字符串键位于路径查找的末尾时,它不会查看 数组。

但是,在 中Amazon DocumentDB,只读取数组中的首个元素,因为 getArrayIndexFromKeyString 会将空字符串转换为 0,因此字符串键查找将被视为数组索引查找。

MongoDB APIs、操作和数据类型

Amazon DocumentDB 与 MongoDB 3.6 和 4.0 兼容APIs。 有关支持的功能的最新列表,请参阅支持的 MongoDB 、 APIs操作和 数据类型

mongodumpmongorestore 实用程序

Amazon DocumentDB 不支持管理数据库,因此在使用 mongodumpmongorestore 实用程序时不转储或还原管理数据库。当您在 Amazon DocumentDB 中使用 mongorestore 创建新的数据库时,除了执行还原操作外,还需要重新创建用户角色。

结果排序

Amazon DocumentDB不保证结果集的隐式结果排序顺序。要确保结果集的顺序,可使用 显式指定排序顺序。sort().

以下示例根据库存字段按降序对清单集合中的项目排序。

db.inventory.find().sort({ stock: -1 })

使用$sort聚合阶段时,除非$sort该阶段是聚合管道中的最后一个阶段,否则排序顺序不会保留。当$sort将聚合阶段与$group聚合阶段结合使用时$sort,聚合阶段仅应用于 $first$last 累加器。在 Amazon DocumentDB 4.0 中,增加了对 的支持$push,以遵守上一$sort阶段的排序顺序。

可重试写入

从MongoDB与 4.2 兼容的驱动程序开始,默认情况下启用可重试写入。但是,当前 Amazon DocumentDB 不支持可重试写入。该功能差异将显示在类似以下内容的错误消息中。

{"ok":0,"errmsg":"Unrecognized field: 'txnNumber'","code":9,"name":"MongoError"}

可重试写入可以通过连接字符串(例如 MongoClient("mongodb://my.mongodb.cluster/db?retryWrites=false")) )或构造MongoClient函数的关键字参数(例如 )禁用MongoClient("mongodb://my.mongodb.cluster/db", retryWrites=False))

下面是一个在连接字符串中禁用可重试写入的 Python 示例。

client = pymongo.MongoClient('mongodb://<username>:<password>@docdb-2019-03-17-16-49-12.cluster-ccuszbx3pn5e.us-east-1.docdb.amazonaws.com:27017/?replicaSet=rs0',w='majority',j=True,retryWrites=False)

稀疏索引

要使用您在查询中创建的稀疏索引,必须在涵盖索引的字段中使用 $exists 子句。如果省略 $exists,Amazon DocumentDB 将不使用稀疏索引。

以下是示例。

db.inventory.count({ "stock": { $exists: true }})

对于稀疏的多键索引,如果查找文档生成了一组值并且只缺少了部分索引字段,则 Amazon DocumentDB 不支持唯一键约束。例如,如果输入为 createIndex({"a.b" : 1 }, { unique : true, sparse :true }),则 "a" : [ { "b" : 2 }, { "c" : 1 } ] 不受支持,因为 "a.c" 存储在索引中。

存储压缩

Amazon DocumentDB 目前不支持对存储的数据或索引进行压缩。存储的数据和索引的数据大小可能比您使用其他选项时更大。

在 $all 表达式中使用 $elemMatch

当前,Amazon DocumentDB 不支持在 $elemMatch 表达式中使用 $all 运算符。一种解决方法是,您可以将 $and 运算符与 $elemMatch 结合使用,如下所示。

原始运算:

db.col.find({ qty: { $all: [ { "$elemMatch": { part: "xyz", qty: { $lt: 11 } } }, { "$elemMatch": { num: 40, size: "XL" } } ] } })

更新后的运算:

db.col.find({ $and: [ { qty: { "$elemMatch": { part: "xyz", qty: { $lt: 11 } } } }, { qty: { "$elemMatch": { qty: 40, size: "XL" } } } ] })

$distinct$elemMatch索引

Amazon DocumentDB 目前不支持将索引与 $distinct$elemMatch 运算符一起使用的功能。因此,使用这些运算符将导致集合扫描。通过在使用以上运算符之一之前执行筛选或匹配,将减少需要扫描的数据量,从而提高性能。

Amazon DocumentDB 目前不支持将索引与 $ne、、$nin$nor$not$exists $distinct $elemMatch 运算符一起使用的功能。因此,使用这些运算符将导致集合扫描。通过在使用以上运算符之一之前执行筛选或匹配,将减少需要扫描的数据量,从而提高性能。

$lookup

Amazon DocumentDB 支持进行相等匹配(例如,左外联接),但不支持不相关的子查询。

使用 利用索引 $lookup

现在,您可以将索引与 $lookup 阶段运算符结合使用。根据您的使用案例,有多种索引算法可用于优化性能。本部分将介绍 的不同索引算法$lookup,并帮助您选择最适合您的工作负载的不同索引算法。

默认情况下,当使用 时, 将利用哈希算法Amazon DocumentDB;当使用 时allowDiskUse:falseallowDiskUse:true将排序合并。对于某些使用案例,可能需要强制查询优化程序使用其他算法。以下是$lookup聚合运算符可以使用的不同索引算法:

  • 嵌套循环: A nested loop plan is typically beneficial for a workload if the foreign collection is <1 GB and the field in the foreign collection has an index. If the nested loop algorithm is being used, the explain plan will show the stage as NESTED_LOOP_LOOKUP.
  • 排序合并: A sort merge plan is typically beneficial for a workload if the foreign collection does not have an index on the field used in lookup and the working dataset doesn’t fit in memory. If the sort merge algorithm is being used, the explain plan will show the stage as SORT_LOOKUP.
  • 哈希: A hash plan is typically beneficial for a workload if the foreign collection is < 1GB and the working dataset fits in memory. If the hash algorithm is being used, the explain plan will show the stage as HASH_LOOKUP.

您可以通过在查询中使用 explain 来确定用于$lookup运算符的索引算法。下面是一个示例。

db.localCollection.explain(). aggregate( [ { $lookup: { from: "foreignCollection", localField: "a", foreignField: "b", as: "joined" } } ] output { "queryPlanner" : { "plannerVersion" : 1, "namespace" : "test.localCollection", "winningPlan" : { "stage" : "SUBSCAN", "inputStage" : { "stage" : "SORT_AGGREGATE", "inputStage" : { "stage" : "SORT", "inputStage" : { "stage" : "NESTED_LOOP_LOOKUP", "inputStages" : [ { "stage" : "COLLSCAN" }, { "stage" : "FETCH", "inputStage" : { "stage" : "COLLSCAN" } } ] } } } } }, "serverInfo" : { "host" : "devbox-test", "port" : 27317, "version" : "3.6.0" }, "ok" : 1 }

作为使用 explain() 方法的替代方案,您可以使用分析器来检查使用 $lookup 运算符时所使用的算法。有关分析器的更多信息,请参阅分析 Amazon DocumentDB 操作

使用 planHint

如果您希望强制查询优化程序将其他索引算法与 一起使用$lookup,则可以使用 planHint。 为此,请使用聚合阶段选项中的注释来强制实施不同的计划。下面是注释语法的示例:

comment : { comment : “<string>”, lookupStage : { planHint : “SORT” | “HASH” | "NESTED_LOOP" } }

以下是使用 planHint 强制查询优化程序使用HASH索引算法的示例:

db.foo.aggregate( [ { $lookup: { from: "foo", localField: "_id", foreignField: "_id", as: "joined" }, } ], { comment : "{ \\"lookupStage\\" : { \\"planHint\\": \\"HASH\\" }}"

要测试哪种算法最适合您的工作负载,您可以使用 executionStats 方法的 explain 参数来测量$lookup阶段的执行时间,同时修改索引算法(即 HASH/SORT//NESTED_LOOP)。

以下示例演示如何使用 executionStats 通过 $lookup 算法测量SORT阶段的执行时间。

db.foo.explain(“executionStats”).aggregate( [ { $lookup: { from: "foo", localField: "_id", foreignField: "_id", as: "joined" }, } ], { comment : "{ \\"lookupStage\\" : { \\"planHint\": \\"SORT\\" }}"