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
相关产品推荐
相关产品推荐

