You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB带索引仍查询缓慢问题排查(PyMongo 4.8.0)

PyMongo查询性能异常排查:索引已存在但查询耗时过长

环境与代码背景

使用PyMongo 4.8.0,网页爬虫完成商品抓取后,在管道中创建索引,相关代码如下:

utils.py

class DecimalCodec(TypeCodec):
    python_type = Decimal
    bson_type = Decimal128

    def transform_python(self, value):
        return Decimal128(value)
    
    def transform_bson(self, value):
        return value.to_decimal()

decimal_codec = DecimalCodec()
type_registry = TypeRegistry([decimal_codec])
codec_options = CodecOptions(type_registry=type_registry)

def get_mongo_client():
    return MongoClient(settings.MONGODB_SERVER, settings.MONGODB_PORT)

def get_mongo_db():
    return get_mongo_client().get_database(
        settings.MONGODB_DB, codec_options=codec_options
    )

爬虫Pipeline代码

class BasePipeline:
    def open_spider(self, spider):     
        self.product_collection = get_mongo_db()[settings.PRODUCT_COLLECTION_NAME]
        # ... 其他逻辑
        self.product_collection.create_index([("import_run_id", -1)])

问题现象

  1. 确认索引已存在:
In [8]: list(get_mongo_db()[settings.PRODUCT_COLLECTION_NAME].list_indexes()) 
Out[8]: [ SON([('v', 2), ('key', SON([('_id', 1)])), ('name', '_id_')]), SON([('v', 2), ('key', SON([('import_run_id', -1)])), ('name', 'import_run_id_-1')]) ]
  1. 集合约含30万条数据,执行以下查询耗时56.496秒:
In [7]: t1 = time.time()
...: x = [p for p in get_mongo_db()[settings.PRODUCT_COLLECTION_NAME].find(
...:     {"import_run_id": 245})]; t2 = time.time(); print(t2-t1)

疑问:是否是列表推导导致查询缓慢?

查询执行计划分析(explain结果)

提取.explain().executionStats的转换结果(RawBSON转JSON):

{
    'executionSuccess': true,  // 查询执行成功
    'nReturned': 5885,  // 返回文档数
    'executionTimeMillis': 61,  // 数据库侧查询耗时(毫秒)
    'totalKeysExamined': 5885,  // 检查的索引键总数
    'totalDocsExamined': 5885,  // 检查的文档总数
    'executionStages': {  // 查询执行阶段详情
        'stage': 'FETCH',  // 文档获取阶段
        'inputStage': {  // 输入阶段
            'stage': 'IXSCAN',  // 索引扫描阶段
            'keyPattern': {  // 使用的索引模式
                'import_run_id': -1
            },
            'indexName': 'import_run_id_-1',  // 索引名称
            'isMultiKey': false,  // 非多键索引
            'multiKeyPaths': {  // 多键路径为空
                'import_run_id': []
            },
            'isUnique': false,  // 非唯一索引
            'isSparse': false,  // 非稀疏索引
            'isPartial': false,  // 非部分索引
            'indexVersion': 2,  // 索引版本
            'direction': 'forward',  // 扫描方向
            'indexBounds': {  // 索引扫描范围
                'import_run_id': [245, 245]
            },
            'keysExamined': 5885,  // 检查的索引键数量
            'seeks': 1,  // 索引查找次数
            'dupsTested': 0,  // 无重复键检测
            'dupsDropped': 0  // 无重复键丢弃
        }
    },
    'executionTimeMillisEstimate': 61,  // 预估执行耗时
    'works': 5885,  // 总工作量
    'advanced': 5885,  // 推进的文档数
    'needTime': 0,  // 无需额外等待时间
    'needYield': 0,  // 无需让出资源
    'saveState': 194,  // 状态保存次数
    'restoreState': 194,  // 状态恢复次数
    'isEOF': 1,  // 已到结果末尾
    'docsExamined': 5885,  // 检查的文档数
    'alreadyHasObj': 0,  // 无缓存文档
    'allPlansExecution': []  // 无其他执行计划
}

从执行计划可以看出:数据库层面的查询仅耗时61毫秒,说明慢的根源不在数据库查询本身。

结论与优化建议

  • 列表推导不是核心问题,但它强制将所有查询结果加载到内存并转换为Python对象,这才是耗时主因:
    • PyMongo的find()返回游标,默认不会立即加载所有数据;列表推导会遍历游标,把BSON文档转换为Python字典,加上自定义DecimalCodec对Decimal128的转换,会产生额外开销
    • 网络传输(如果MongoDB不在本地)、序列化/反序列化5885条文档的过程,是耗时的主要来源
  • 优化方向:
    • 只查询需要的字段:通过find的第二个参数指定返回字段,减少数据传输量,例如:
      find({"import_run_id": 245}, {"_id": 1, "title": 1, "price": 1})
      
    • 分批处理数据:避免一次性加载全量数据,使用游标分批遍历,例如:
      cursor = get_mongo_db()[settings.PRODUCT_COLLECTION_NAME].find({"import_run_id": 245}).batch_size(1000)
      for doc in cursor:
          # 处理单条文档
          pass
      
    • 检查网络延迟:如果MongoDB部署在远程服务器,优先排查网络链路的耗时
    • 优化Codec性能:评估自定义DecimalCodec的转换逻辑是否有优化空间,比如批量转换或简化处理

内容的提问来源于stack exchange,提问作者Džiugas Bižokas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:40:55