对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。

使用桶模式对数据进行分组

存储桶模式将长系列数据分成不同的对象。将大型数据序列分成较小的群组可以改善查询访问模式并简化应用程序逻辑。当您拥有与中心实体相关的类似对象(例如单个用户进行的股票交易)时,存储桶很有用。

您可以使用桶模式进行分页,根据应用程序每页显示的元素数量对数据进行分组。此方法使用 MongoDB 的灵活数据模型,根据应用程序所需的数据存储数据。

提示

时间序列集合会自动应用存储桶模式,适用于大多数涉及对时间序列数据进行分桶的应用程序。

考虑以下追踪股票交易的模式。初始模式未使用存储桶模式,并将每笔交易存储在单个文档中。

db.trades.insertMany( [ { "ticker" : "MDB", "customerId": 123, "type" : "buy", "quantity" : 419, "date" : ISODate("2023-10-26T15:47:03.434Z") }, { "ticker" : "MDB", "customerId": 123, "type" : "sell", "quantity" : 29, "date" : ISODate("2023-10-30T09:32:57.765Z") }, { "ticker" : "GOOG", "customerId": 456, "type" : "buy", "quantity" : 50, "date" : ISODate("2023-10-31T11:16:02.120Z") } ])

该应用程序每次显示单个客户的股票交易,每页显示 10 笔交易。为了简化应用程序逻辑,请使用存储桶模式按 customerId 对交易进行分组,每组 10。

1

重新组织模式,使每个 customerId 都有一个文档:

{ "customerId": 123, "history": [ { "type": "buy", "ticker": "MDB", "qty": 419, "date": ISODate("2023-10-26T15:47:03.434Z") }, { "type": "sell", "ticker": "MDB", "qty": 29, "date": ISODate("2023-10-30T09:32:57.765Z") } ]},{ "customerId": 456, "history": [ { "type" : "buy", "ticker" : "GOOG", "quantity" : 50, "date" : ISODate("2023-10-31T11:16:02.120Z") } ]}

使用桶模式:

  • 具有通用 customerId 值的文档将压缩到单个文档中,其中 customerId 为顶级字段。

  • 该客户的交易被分组到一个名为 history 的嵌入式数组字段中。

2
db.trades.drop() db.trades.insertMany( [ { "_id": "123_1698349623", "customerId": 123, "count": 2, "history": [ { "type": "buy", "ticker": "MDB", "qty": 419, "date": ISODate("2023-10-26T15:47:03.434Z") }, { "type": "sell", "ticker": "MDB", "qty": 29, "date": ISODate("2023-10-30T09:32:57.765Z") } ] }, { "_id": "456_1698765362", "customerId": 456, "count": 1, "history": [ { "type" : "buy", "ticker" : "GOOG", "quantity" : 50, "date" : ISODate("2023-10-31T11:16:02.120Z") } ] }, ])

_id 字段值是 customerId 和 history 字段中首次交易时间(以秒为单位,自 Unix 纪元以来)的组合。

count字段指示该文档的history大量中有多少个元素。 count字段用于实现分页逻辑。

更新模式以使用桶模式后,请更新用于读取和写入数据的应用程序逻辑。请参阅以下部分:

在更新的模式中,每个文档都包含应用程序中单个页面的数据。您可以使用 _id 和 count 字段来确定如何返回和更新数据。

要查询相应页面的数据,可使用 regex 查询返回指定 customerId 的数据,并使用 skip 返回正确页面的数据。对 _id 的 regex 查询使用默认 _id 索引,因此无需额外索引即可实现高性能查询。

以下查询返回客户 123 第一页交易的数据:

db.trades.find( { "_id": /^123_/ } ).sort( { _id: 1 } ).limit(1)

要返回后续页面的数据,请指定一个 skip 值,该值比要显示其数据的页面小 1。例如,要显示页面 10 的数据,请运行以下查询:

db.trades.find( { "_id": /^123_/ } ).sort( { _id: 1 } ).skip(9).limit(1)

注意

前面的查询没有返回结果,因为示例数据仅包含第一页的文档。

既然该模式使用了桶模式,请更新您的应用程序逻辑以将新的交易插入到正确的桶中。使用更新命令,通过适当的 customerId 和桶将交易插入到桶中。

以下命令为 customerId: 123 插入一项新交易:

db.trades.updateOne( { "_id": /^123_/, "count": { $lt: 10 } }, { "$push": { "history": { "type": "buy", "ticker": "MSFT", "qty": 42, "date": ISODate("2023-11-02T11:43:10") } }, "$inc": { "count": 1 }, "$setOnInsert": { "_id": "123_1698939791", "customerId": 123 } }, { upsert: true })

应用程序每页显示10笔交易。 更新过滤搜索文档中的customerId: 123 ,其中count小于10 ,即存储桶不包含整页数据。

  • 如果存在与"_id": /^123_/匹配的文档,且其count小于10 ,则更新命令会将新交易推送到匹配文档的history大量中。

  • 如果没有匹配的文档,更新命令将插入包含新交易的新文档(因为upsert是true )。 新文档的_id字段是customerId和自交易的Unix纪元以来的时间(以秒为单位)的串联。

更新命令的逻辑通过确保没有 history 数组包含超过 10 个文档来避免无界数组。

运行更新操作后,trades 集合会包含以下文档:

[ { _id: '123_1698349623', customerId: 123, count: 3, history: [ { type: 'buy', ticker: 'MDB', qty: 419, date: ISODate("2023-10-26T15:47:03.434Z") }, { type: 'sell', ticker: 'MDB', qty: 29, date: ISODate("2023-10-30T09:32:57.765Z") }, { type: 'buy', ticker: 'MSFT', qty: 42, date: ISODate("2023-11-02T11:43:10.000Z") } ] }, { _id: '456_1698765362', customerId: 456, count: 1, history: [ { type: 'buy', ticker: 'GOOG', quantity: 50, date: ISODate("2023-10-31T11:16:02.120Z") } ] }]

实现存储桶模式后,您无需纳入分页逻辑即可在应用程序中返回结果。 数据的存储方式与其在应用程序中的使用方式相匹配。