并发 upsert 会不会重复插入?E11000 什么时候会露给应用?这篇用 16 个线程在同一个条件上并发 upsert,对比有无唯一索引,并给出应用层的处理办法。
文档总数 632(应为 300 个不同的 k);有重复的 k 有 227 个,最多重复 5 份;错误数 0
upsert 不是原子的“不存在才插入”:并发时多个线程都查不到,各自插入,300 个 k 里 227 个被重复,最多 5 份,而且没有任何错误。
文档总数 300(应为 300);客户端收到的错误数 0 (服务端对“条件里是唯一键的等值”的 upsert 在遇到 E11000 时会自动重试一次,所以一般看不到)
唯一索引挡住重复,文档数正好 300;对条件是唯一键等值的 upsert,服务端遇到 E11000 会自动重试一次,客户端看不到错误。
3 轮,每轮 16 个线程;文档数 1(唯一的 b="same");错误数 32,例如 ['DuplicateKeyError: Plan executor error during update :: caused by :: E11000 duplicate key', 'DuplicateKeyError: Plan executor error during update :: caused by :: E11000 duplicate key']
条件不包含唯一键(例如多了别的字段,或唯一键值由 $setOnInsert 给出)时,服务端不重试,E11000 会作为 DuplicateKeyError 返回给应用。
条件 {k, tag: "x"}(多了一个非唯一字段 tag,服务端不再自动重试):捕获到 DuplicateKeyError 22 次,重试更新成功 22 次;最终文档数 60(60 个 k)捕获 DuplicateKeyError,改成不 upsert 的更新再来一次(此时文档已被别人插入)。
16 个线程各取 100 次:共 1600 个编号,不同的 1600 个,最小 1,最大 1600(无重复无缺口)
findOneAndUpdate 加 $inc 是原子的,1600 次得到 1600 个不同的编号。
test> db.s.updateOne({k: 1}, {$set: {v: 1}, $setOnInsert: {created: "first"}}, {upsert: true})
{
acknowledged: true,
insertedId: ObjectId('6acc4b68107af69346916ed2'),
matchedCount: 0,
modifiedCount: 0,
upsertedCount: 1
}
test> 第二次 upsert(文档已存在,$setOnInsert 不生效)
{ k: 1, created: 'first', v: 2 }只在发生插入时设置字段,已存在时保持原值。