如何原子地弹出随机元素?

发布于 2024-12-02 14:30:54 字数 382 浏览 1 评论 0原文

有没有一种方法可以使用 MongoDB 以原子方式弹出(删除和检索)随机元素 - 就像 Redis 的 SPOP 一样?

我已阅读 RandomAttribute 教程,但现在我需要确保该元素是获取时也会被删除,并且这必须以原子方式完成。

我想作为替代方案,我可以将数据推送到预先排序的数组字段中,但我真的更喜欢让它获取随机记录。

查看 $pop 的文档,它似乎不能接受参数,因此它会删除数组的第一个或最后一个元素。

Is there a way to atomically pop (remove and retrieve) a random element with MongoDB - like Redis's SPOP?

I've read the RandomAttribute tutorial but now I need to make sure that the element is also removed when fetched, and this must be done atomically.

I guess as an alternative I could push the data into an array field pre-sorted, but I'd really prefer to have it fetch a random record.

Looking at $pop's documentation, it seems it can't take arguments, so it either removes the first or the last element of an array.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

樱&纷飞 2024-12-09 14:30:54

Mongo 中集合内的更新是原子的,因此将 findAndModify 与 RandomAttribute 方法相结合即可实现这一点:

var rand = Math.rand();
db.collection.findAndModify({query: {random: {$gte: rand}}, remove: true});

只需确保在以下情况下也查询 random: {$lt: rand}上面返回null

专业提示:某些驱动程序具有 findAndRemove,这与带有 remove: truefindAndModify 相同。

Updates within collections in Mongo are atomic, so combining findAndModify with the RandomAttribute approach will do the trick:

var rand = Math.rand();
db.collection.findAndModify({query: {random: {$gte: rand}}, remove: true});

Just make sure to also query for random: {$lt: rand} when the above returns null.

Protip: some drivers have findAndRemove, which is the same as findAndModify with remove: true.

残疾 2024-12-09 14:30:54

目前基本上没有一个好的方法来做你想做的事情。最大的问题是没有一种原子方法可以按位置删除数组元素。

请参阅此问题了解有关于此的更多信息。

我能想到的解决这个问题的最好的(最接近原子的)方法如下。它依赖于您能够在短时间内处理数组中的空元素(这不是原子的)。

第一步

在客户端生成一个随机数。使用它来指定要以点表示法删除的数组元素的键(即“my_array.4”),因为据我所知,这不能在查询中动态完成。

第二步

使用随机数以原子方式取消设置数组中选定的元素,同时使用 findAndModify检索更新之前的元素。 >new 设置为 false。假设您生成的随机数是 4,您将执行类似以下操作:(

db.mycollection.findAndModify({
  query: {_id: 1},
  update: {$unset: {"my_array.4" : true }},
  fields: {my_array : {$slice : [4, 1]}, _id : false},
  new: false
});

注意。findAndModify 目前默认返回更新前的文档,但我在某处读到这可能会发生变化,因此最好始终使用 new 选项指定您想要的元素。)

这会将被选为 null 值的数组元素保留在数组。

第三步

使用 $pull 进行另一次更新,从数组中删除 null 元素:

db.mycollection.update({_id: 1}, {$pull: {my_array: null}})

唷!对于你想做的事情来说,似乎需要做很多工作。我不了解 Redis,但 SPOP 听起来容易多了。无论如何,我希望这能有所帮助。这不是单个原子操作,但是(只要您可以处理这些空值)我认为它是最重要的部分。你永远不会遇到两个不同的线程几乎同时弹出同一个元素的情况,我猜这就是你想要避免的情况。

There basically isn't currently a nice way to do what you want to do. The big problem is that there isn't an atomic way to delete an element of an array by its position.

See this question for more info on this.

The best - nearest to atomic - way I can think of to work around this is the following. It relies on you being able to handle null elements in the array for a short time (this is the bit that isn't atomic).

Step One

Generate a random number on the client side. Use this to specify the key of the array element you want to remove in dot notation (ie. "my_array.4") as AFAIK this can't be done dynamically in the query.

Step Two

Use the random number to atomically unset the selected element of the array while retrieving it as it was before the update using findAndModify with new set to false. Assuming the random number you generated was 4, you'd do something like the following:

db.mycollection.findAndModify({
  query: {_id: 1},
  update: {$unset: {"my_array.4" : true }},
  fields: {my_array : {$slice : [4, 1]}, _id : false},
  new: false
});

(NB. findAndModify currently returns the document pre-update by default, but I read somewhere that that might change, so it's a good idea always to specify which you want using the new option.)

This will leave the array element that was selected as a null value in the array.

Step Three

Remove the null element from the array with another update, using $pull:

db.mycollection.update({_id: 1}, {$pull: {my_array: null}})

Phew! Seems like a lot of work for what you want to do. I don't know Redis but SPOP sounds a lot easier. I hope this helps a bit though, anyway. It's not a single atomic operation, but (as long as you can handle those nulls) I think the most important parts of it are. You're never going to get into a situation where two different threads pop the same element at nearly the same time, which, I'm guessing, is what you're trying to avoid.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文