javascript – 使用mongoose中间件删除依赖文档时出现并发问题

假设我们有一个简单的应用程序,用户可以创建产品并对其进行评论.产品和评论的架构可以是:

var productSchema = new mongoose.Schema({
  author_id: ObjectId,
  description: String
});

var commentSchema = new mongoose.Schema({
  product_id: ObjectId,
  author_id: ObjectId,
  message: String
});

我们希望确保每条评论都涉及现有产品.这可以通过mongoose pre save hook轻松完成:

commentSchema.pre("save", function(next) {
  Product.count({ _id: this.product_id }, function(err, count) {
    if (err || !count) {
      next(new Error("Could not find product"));
    } else {
      next();
    }
  });
});

此外,如果用户删除了产品,我们还要删除该产品的所有评论.这可以使用预先删除钩子轻松完成:

productSchema.pre("remove", function(next) {
  Comment.remove({ product_id: this._id }, next);
});

但是,如果用户A删除了产品并且同时用户B对该产品发表了评论,该怎么办?

可能发生以下情况:

Call pre save hook for new comment, and check if product exists
Call pre remove hook for product, and remove all comments
In pre save hook, done checking: product actually exists, call next
Comment saved
In pre remove hook, done removing comments: call next
Product removed

最终结果是我们有一条评论指的是不存在的产品.

这只是导致这种情况发生的众多案例之一.如何防止这种角落的情况?

最佳答案 似乎使用猫鼬后挂钩而不是预挂钩解决了这个问题:

commentSchema.post("save", function(comment) {
  Product.count({ _id: comment.product_id }, function(err, count) {
    if (err || !count) comment.remove();
  });
});

productSchema.post("remove", function(product) {
  Comment.remove({ product_id: product._id }).exec();
});

让我们看看为什么这可以通过考虑四种可能的情况(我能想到)来解决问题:

1) Comment gets saved before product is removed
2) Comment gets saved after product is removed but before post remove hook
3) Comment gets saved after product is removed and while post remove hook is 
   executing
4) Comment gets saved after product is removed and post remove hook executed
------------------------------------------------------------------------
In case 1, after the product is removed, the comment will be removed in the post 
remove hook.
In case 2, same, post remove hook will remove the comment.
In case 3, the comment post save hook will successfully remove the comment.
In case 4, same as case 3, post save hook removes the comment.

但是仍然存在一个小问题:如果在删除产品之后但在执行post remove钩子之前发生了什么不好怎么办?说断电或类似的东西.在这种情况下,我们最终会得到引用不存在的产品的评论.为了解决这个问题,我们可以在产品上保留预先删除钩子.这样可以确保仅删除产品,并且仅在删除所有相关注释时才删除产品.然而,正如OP指出的那样,这并不处理并发性问题,这就是我们的帖子删除钩子来救援的地方!所以我们都需要:

productSchema.pre("remove", function(next) {
  var product = this;
  Comment.remove({ product_id: product._id }, next);
});

productSchema.post("remove", function(product) {
  Comment.remove({ product_id: product._id }).exec();
});

我希望就是这样,但我仍然可以想到一个非常遥远的案例:如果在删除产品后删除注释并删除钩子执行后,如果在注释帖保存钩子执行之前(这将删除注释),该怎么办?灯灭了!我们最终得到的评论指的是不存在的产品!发生这种情况的几率非常低,但仍然……

如果有人能想出更好的处理并发的方法,请改进我的答案或自己编写!

点赞