Azure CosmosDB(MongoDB API)+PyMongo:数组字段意外替换为过滤器子文档排查
问题描述
我在Flask服务中使用PyMongo操作Azure上的CosmosDB(MongoDB API),集合中文档结构如下:
{ "file_name": "foo", "date": "2024-05-07", "count": 1, "deliveries": [ { ..., "events": [ ... ], "type": "NG" }, { ..., "events": [ ... ], "type": "Legacy" } ], "snapshot_timestamp": 1234567 }
我通过一系列更新函数操作文档,示例代码如下:
collection.update_one( { "file_name": ..., "snapshot_timestamp": ..., }, { "$set": { "deliveries.$[outer].type": "NG", "deliveries.$[outer].foo": "bar", "count": 5 }, "$push": { "deliveries.$[outer].some_array": "some_value" } }, upsert=True, array_filters=[ {"outer.x": "y"} ] )
但偶尔会出现损坏的文档,deliveries数组被替换为包含$[outer]键的对象,结构如下:
{ "file_name": "foo", "date": "2024-05-07", "count": 1, "deliveries": { "$[outer]": { ..., "events": [ ... ], "type": "NG" }, }, "snapshot_timestamp": 1234567 }
我并未编写过直接执行"$set": {"delivery.$[outer]"}的更新操作,该异常极少出现且无明确触发路径,请问这种情况是否可能由update操作导致?
分析与结论
这种异常确实有可能是当前的update_one操作导致的,核心原因在于**upsert=True的配合逻辑**:
- 当查询条件(
file_name和snapshot_timestamp)匹配不到任何文档时,MongoDB会执行插入新文档的操作。但此时array_filters里的条件无法作用于不存在的数组,更新语句中的deliveries.$[outer].xxx路径会被解析为创建一个名为deliveries的对象,其中包含$[outer]子对象,而非创建数组。 - 这种情况只会在首次upsert触发且数组过滤器无匹配的场景下发生,高并发场景下的竞态(比如多个请求同时尝试upsert同一条未存在的文档)、偶尔出现的查询条件不匹配已有文档的边缘情况,都可能触发这个问题。
解决建议:
- 若业务允许,尽量避免在包含数组过滤器的更新操作中使用
upsert=True,改为先查询再判断是否插入,插入时明确初始化deliveries为数组结构。 - 必须使用upsert的话,拆分更新逻辑:在upsert时通过
$setOnInsert明确初始化deliveries为数组,$set和$push仅作用于已有文档的更新,示例如下:
collection.update_one( { "file_name": ..., "snapshot_timestamp": ..., }, { "$setOnInsert": { "date": "2024-05-07", "count": 1, "deliveries": [] # 明确初始化数组 }, "$set": { "deliveries.$[outer].type": "NG", "deliveries.$[outer].foo": "bar", "count": 5 }, "$push": { "deliveries.$[outer].some_array": "some_value" } }, upsert=True, array_filters=[ {"outer.x": "y"} ] )
这样即使触发upsert,deliveries会被初始化为数组,后续的数组过滤器操作不会破坏结构。
内容的提问来源于stack exchange,提问作者777moneymaker
相关产品推荐
相关产品推荐

