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

PostgreSQL查询过慢:Django百万级数据查询优化求助

问题

我正在开发一个基于Django的Web项目,使用PostgreSQL作为数据库。随着数据量增长,系统查询速度逐渐变慢。核心表Produto已有数百万条记录,诸如商品列表这类简单查询耗时超出预期。我已为相关字段添加了部分索引,但仍认为有优化空间。

模型定义:

class Produto(models.Model):
    nome = models.CharField(max_length=255)
    descricao = models.TextField()
    preco = models.DecimalField(max_digits=10, decimal_places=2)
    estoque = models.IntegerField()

class Pedido(models.Model):
    produtos = models.ManyToManyField(Produto)
    data_pedido = models.DateTimeField(auto_now_add=True)

执行的查询语句:

produtos = Produto.objects.filter(estoque__gt=0).order_by('-preco')[:10]

请问我存在哪些问题?该如何优化此查询?


现存问题分析

  • 索引匹配度不足:你添加的部分索引大概率是单字段索引(比如单独给estoque或preco加索引),但当前查询是过滤estoque>0+按preco降序排序+取前10条的组合逻辑,单字段索引无法覆盖这个场景,数据库需要先过滤再排序,触发文件排序(filesort)操作,这在百万级数据下会严重拖慢速度。
  • 未使用覆盖索引:默认查询会返回Produto的所有字段,即使前端只需要部分字段,这会导致数据库从索引找到匹配记录后,还要回表读取完整数据,增加IO开销。
  • 潜在的统计信息过时:PostgreSQL的查询优化器依赖表的统计信息,如果统计信息过时,可能会选择低效的执行计划(比如全表扫描而非索引扫描)。

优化方案

1. 创建匹配查询逻辑的复合部分索引

针对你的查询场景,创建一个只包含estoque>0记录、按preco降序排列的复合部分索引,让数据库可以直接从索引中获取排序好的前10条数据,完全避免排序和过滤的额外开销。

在Django模型中添加:

class Produto(models.Model):
    nome = models.CharField(max_length=255)
    descricao = models.TextField()
    preco = models.DecimalField(max_digits=10, decimal_places=2)
    estoque = models.IntegerField()

    class Meta:
        indexes = [
            # 核心复合部分索引,匹配过滤+排序逻辑
            models.Index(
                fields=['-preco'],
                condition=models.Q(estoque__gt=0),
                name='produto_estoque_positivo_preco_desc_idx'
            ),
        ]

如果前端只需要nome、preco、estoque这几个字段,可以进一步把这些字段加入索引,做成覆盖索引,彻底消除回表操作:

models.Index(
    fields=['-preco', 'nome', 'estoque'],
    condition=models.Q(estoque__gt=0),
    name='produto_estoque_positivo_preco_desc_cover_idx'
),

2. 只查询需要的字段

避免查询不必要的字段(比如大文本字段descricao),减少数据传输和IO开销:

produtos = Produto.objects.filter(estoque__gt=0)\
                          .order_by('-preco')\
                          .only('nome', 'preco', 'estoque')[:10]

3. 验证查询执行计划

用Django的explain()方法确认索引是否生效,检查执行计划中是否使用了我们创建的索引:

print(Produto.objects.filter(estoque__gt=0).order_by('-preco')[:10].explain(analyze=True))

如果输出中出现Index Scan using produto_estoque_positivo_preco_desc_idx,说明索引生效;如果是Seq Scan(全表扫描)或Sort(文件排序),需要检查索引是否正确创建,或者执行ANALYZE Produto;更新PostgreSQL的统计信息。

4. 归档冷数据

如果有大量estoque<=0的历史商品,可以将这些数据归档到单独的历史表,减少主表Produto的数据量,提升查询效率。


内容的提问来源于stack exchange,提问作者LucasBR

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 12:26:01