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

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

功能差异: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 操作中每个单独的插入语句都将作为原子操作执行。如果您需要 ACID 属性insertManyupdateMany, 和deleteMany操作时,建议使用事务。

更新的功能差异

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 现在支持空字符('\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 次或更少的字段上创建索引。

嵌套文档的投影

有一个功能区别$project运算符在版本 3.6 中已在 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}}}那么 Amazon DocumentDB 3.6 只会将投影应用于a而不是b或者c. 在 Amazon DocumentDB 4.0 中,{a:{b:{c:1}}}将应用于ab, 和c.

与 MongoDB 之间的功能差异

管理数据库和集合

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

cursormaxTimeMS

在 Amazon DocumentDB 中,cursor.maxTimeMS重置每个计数器getMore请求. 因此,如果一个 3000MSmaxTimeMS,则查询需要 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 API、操作和数据类型

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

mongodumpmongorestore实用程序

Amazon DocumentDB 不支持管理数据库,因此在使用mongodump或者mongorestore实用工具。当您在 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 不支持唯一键约束。例如,如果输入为 "a" : [ { "b" : 2 }, { "c" : 1 } ],则 createIndex({"a.b" : 1 }, { unique : true, sparse :true }) 不受支持,因为 "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" } } } ] })

$ne$nin$nor$not$exists, 和$elemMatch索引

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

$lookup

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

将索引用于$lookup

现在,您可以使用带有$lookup阶段运算符。根据您的使用案例,您可以使用多种索引算法来优化性能。本节将介绍不同的索引算法$lookup并帮助您选择最能满足您工作负载的服务。

默认情况下,Amazon DocumentDB 将在allowDiskUse:false使用并排序合并时allowDiskUse: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 嵌套循环查找.
  • 排序合并: 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 排序查找.
  • 哈希: 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 哈希查找.

您可以识别正在用于$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运算符。有关 Profiler 的更多信息,请参阅分析 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\\" }}"

要测试哪种算法最适合您的工作负载,可以使用executionStatsexplain方法来测量$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\\" }}"