我想创建一个一致的Mongo文档的单链列表;也就是说,我希望每个文档只有1个父级和1个子级,并且我希望没有循环。我也希望只有 1 个根。我尝试通过交易完成此操作,但它似乎实际上并没有正确阻塞。
const session = await mongoose.startSession();
session.startTransaction();
const message = await Message
.findOne({ root: root })
.sort({ time: -1 })
.session(session);
const newMessage = await Message.create(
{
time: new Date(),
root: root,
previous: message,
content: content,
},
{ session: session }
);
await session.commitTransaction();
session.endSession();
在这里,我正在尝试获取最新消息,并使用最新消息设置为上一条消息创建新消息,以创建消息链表。但是当我快速连续调用它时(即,让多个客户端尝试同时发布消息),它显然失败了,因为它最终创建了没有空previous
字段的消息;它似乎是并行运行事务,而不是串联运行。它应该等到当前事务完成后再执行findOne
,但看起来它只是运行所有findOne
,然后运行所有create
。这让我相信我一定对我进行交易的方式有问题,但我不知道我做错了什么。
作为参考,下面是使用 10 个客户端运行此代码后的集合。
rs0:PRIMARY> db.messages.find()
{ "_id" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d43"), "time" : ISODate("2020-08-31T23:04:08.347Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b2"), "content" : "Hi guys, my name's 6. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d44"), "time" : ISODate("2020-08-31T23:04:08.350Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b4"), "content" : "Hi guys, my name's 5. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d45"), "time" : ISODate("2020-08-31T23:04:08.351Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b3"), "content" : "Hi guys, my name's 9. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d46"), "time" : ISODate("2020-08-31T23:04:08.356Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781ae"), "content" : "Hi guys, my name's 1. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d47"), "time" : ISODate("2020-08-31T23:04:08.369Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781ad"), "content" : "Hi guys, my name's 2. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d48"), "time" : ISODate("2020-08-31T23:04:08.371Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b5"), "content" : "Hi guys, my name's 7. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d49"), "time" : ISODate("2020-08-31T23:04:08.374Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b1"), "content" : "Hi guys, my name's 8. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d4a"), "time" : ISODate("2020-08-31T23:04:08.377Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b0"), "content" : "Hi guys, my name's 3. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d4b"), "time" : ISODate("2020-08-31T23:04:08.380Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781af"), "content" : "Hi guys, my name's 4. Nice to meet you.", "photos" : [ ], "__v" : 0 }
编辑: 其中一条评论指出我没有使用交易;我更新了代码以使用事务。但是,新代码仍然不起作用。
您可以在previous
字段上创建唯一的分部索引,从索引中排除那些没有该字段的文档。
db.messages.createIndex(
{previous: 1},
{unique: true, partialFilterExpression: {previous: {$exists:true}}}
)
任何尝试为已使用previous
设置值的插入或更新都将收到"E11000 重复键错误"。
如果插入进程收到该错误,它知道其他内容已经链接到它即将链接到的内容,因此它需要再次检查以找到列表的新尾部。