功能差异:Amazon DocumentDB和 MongoDB

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

的功能优势Amazon DocumentDB

隐式事务

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

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

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

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

更新的功能差异

Amazon DocumentDB 通过从客户要求我们构建的功能中向后开发,持续改善与 MongoDB 的兼容性。本部分包含我们已在 Amazon DocumentDB 中删除的功能差异,以便客户能够更轻松地迁移和构建应用程序。

数组索引

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

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

注意

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

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

  1. db.collection.getIndexes()

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

  1. [
  2. {
  3. "v" : 3,
  4. "key" : {
  5. "_id" : 1
  6. },
  7. "name" : "_id_",
  8. "ns" : "test.test"
  9. }
  10. ]

多键索引

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

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

注意

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

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

  1. db.collection.getIndexes()

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

  1. [
  2. {
  3. "v" : 3,
  4. "key" : {
  5. "_id" : 1
  6. },
  7. "name" : "_id_",
  8. "ns" : "test.test"
  9. }
  10. ]

字符串中的 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 和 MongoDB 之间的 Amazon DocumentDB 运算符存在功能差异,该差异已在 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,而不应用于 bc。在 Amazon DocumentDB 4.0 中,投影 {a:{b:{c:1}}} 将应用于 abc

与 MongoDB 之间的功能差异

管理数据库和集合

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

cursormaxTimeMS

在 Amazon DocumentDB 中,cursor.maxTimeMS 为每个 getMore 请求重置计数器。因此,如果指定了 3000MS maxTimeMS,则查询采用 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 中尝试以下命令:

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

MongoDB 将返回以下内容:

  1. WriteResult({ "nInserted" : 1 })

Amazon DocumentDB 将返回错误:

  1. WriteResult({
  2. "nInserted" : 0,
  3. "writeError" : {
  4. "code" : 2,
  5. "errmsg" : "Document can't have $ prefix field names: $a"
  6. }
  7. })

注意

此功能差异有一个例外。以下以 $ 前缀开头的字段名称已列入白名单,并且可以在 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 不支持管理数据库,因此在使用 mongodumpmongorestore 实用程序时不转储或还原管理数据库。当您在 Amazon DocumentDB 中使用 mongorestore 创建新的数据库时,除了执行还原操作外,还需要重新创建用户角色。

结果排序

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

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

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

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

可重试写入

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

  1. {"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 示例。

  1. 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 将不使用稀疏索引。

以下是示例。

  1. 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 结合使用,如下所示。

原始运算:

  1. db.col.find({
  2. qty: {
  3. $all: [
  4. { "$elemMatch": { part: "xyz", qty: { $lt: 11 } } },
  5. { "$elemMatch": { num: 40, size: "XL" } }
  6. ]
  7. }
  8. })

更新后的运算:

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

$distinct$elemMatch 索引

Amazon DocumentDB 目前不支持将索引与 $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 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.

您可以通过对查询使用解释来确定用于 $lookup 运算符的索引算法。以下是示例。

  1. db.localCollection.explain().
  2. aggregate( [
  3. {
  4. $lookup:
  5. {
  6. from: "foreignCollection",
  7. localField: "a",
  8. foreignField: "b",
  9. as: "joined"
  10. }
  11. }
  12. ]
  13. output
  14. {
  15. "queryPlanner" : {
  16. "plannerVersion" : 1,
  17. "namespace" : "test.localCollection",
  18. "winningPlan" : {
  19. "stage" : "SUBSCAN",
  20. "inputStage" : {
  21. "stage" : "SORT_AGGREGATE",
  22. "inputStage" : {
  23. "stage" : "SORT",
  24. "inputStage" : {
  25. "stage" : "NESTED_LOOP_LOOKUP",
  26. "inputStages" : [
  27. {
  28. "stage" : "COLLSCAN"
  29. },
  30. {
  31. "stage" : "FETCH",
  32. "inputStage" : {
  33. "stage" : "COLLSCAN"
  34. }
  35. }
  36. ]
  37. }
  38. }
  39. }
  40. }
  41. },
  42. "serverInfo" : {
  43. "host" : "devbox-test",
  44. "port" : 27317,
  45. "version" : "3.6.0"
  46. },
  47. "ok" : 1
  48. }

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

使用planHint

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

  1. comment : {
  2. comment : “<string>”,
  3. lookupStage : { planHint : SORT | HASH | "NESTED_LOOP" }
  4. }

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

  1. db.foo.aggregate(
  2. [
  3. {
  4. $lookup:
  5. {
  6. from: "foo",
  7. localField: "_id",
  8. foreignField: "_id",
  9. as: "joined"
  10. },
  11. }
  12. ],
  13. {
  14. comment : "{ \\"lookupStage\\" : { \\"planHint\\": \\"HASH\\" }}"

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

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

  1. db.foo.explain(“executionStats”).aggregate(
  2. [
  3. {
  4. $lookup:
  5. {
  6. from: "foo",
  7. localField: "_id",
  8. foreignField: "_id",
  9. as: "joined"
  10. },
  11. }
  12. ],
  13. {
  14. comment : "{ \\"lookupStage\\" : { \\"planHint\": \\"SORT\\" }}"