上下文

我有一个Django REST API,它使用PostgreSQL数据库和数百万项.这些项目由多个系统处理,处理详细信息会发回并存储在记录表中.简化模型包括:

class Item(models.Model):
    details = models.JSONField()


class Record(models.Model):
    items = models.ManyToManyField(Item)
    created = models.DateTimeField(auto_created=True)
    system = models.CharField(max_length=100)
    status = models.CharField(max_length=100)
    details = models.JSONField()

球门

我想对Items表进行任意筛选,并获得各种处理系统的摘要.此摘要获取每个系统的每个选定项目的最新状态,并显示每个状态的计数.例如,如果我筛选1055个项目,返回的示例为:

{
System_1: [running: 5, completed: 1000, error: 50],
System_2: [halted: 55, completed: 1000],
System_3: [submitted: 1055]
}

我目前正在进行如下查询,返回System\u 1的处理状态计数,并重复其他系统的处理状态计数,并将其打包成JSON返回.

Item.objects.filter(....).annotate(
    system_1_status=Subquery(
        Record.objects.filter(
            system='System_1',
            items__id=OuterRef('pk')
        ).order_by('-created').values('status')[:1]
    )
).values('system_1_status').annotate(count=Count('system_1_status'))

这将转换为sql查询:

SELECT 
    "api_item"."id", 
    "api_item"."details", 
    (
        SELECT 
            U0."status" 
        FROM 
            "api_record" U0 
        INNER JOIN 
            "api_record_items" U1 
        ON 
            (U0."id" = U1."record_id") 
        WHERE 
            (U1."item_id" = ("api_item"."id") AND U0."system" = system_1) 
        ORDER BY 
            U0."created" DESC LIMIT 1
    ) AS "system_1_status" 
FROM "api_item"

我们有数以百万计的项目和记录,如果我们 Select 的项目少于1000个,这项工作就相当好了.在这上面需要几分钟.试图为数十万件物品这么做是灾难性的.

问题

我可以改进此查询的性能吗?我不知道怎么做,除了玩索引?

或者,在项目模型中添加一个JSONField来存储该项目的每个系统的最新状态缓存,这会是一个坏主意吗?虽然我不喜欢复制数据的 idea ,但在查询时,对项目模型上已经存在的字段进行聚合应该很快.我有DjangoQ,我可以使用调度函数来保持这些字段最新.

推荐答案

下面的解决方案将样本的查询时间从5分钟减少到20秒.

from collections import Counter

items = Item.objects.filter(...)
{
    "System_1": Counter(
        items.
            filter(record__system='System_1').
            order_by('id', '-record__created').
            values_list('record__status', flat=True).
            distinct('id')),
    "System_2": Counter(
        items.
            filter(record__system='System_2').
            order_by('id', '-record__created').
            values_list('record__status', flat=True).
            distinct('id'))
}

下面给出了最终的PostgreSQL计划,请告诉我是否可以改进:

SELECT DISTINCT ON ("api_item"."id") "api_record"."status" 
FROM "api_item" INNER JOIN "api_record_items" ON ("api_item"."id" = "api_record_items"."item_id") 
INNER JOIN "api_record" ON ("api_record_items"."record_id" = "api_record"."id") 
WHERE "api_record"."system" = System_1 ORDER BY "api_item"."id" ASC, "api_record"."created" DESC

我不喜欢这样,我需要从DB中提取所有值来计算它们,但是我无法使聚合与所需的distinct一起工作,以确保每个项只计算一条记录.

如果任何人都可以改进这一点,那么查询计划是:

Unique  (cost=563327.33..1064477.67 rows=1010100 width=21) (actual time=16194.180..22301.422 rows=1010100 loops=1)
Planning Time: 0.492 ms
Execution Time: 22401.646 ms
  ->  Gather Merge  (cost=563327.33..1053946.33 rows=4212534 width=21) (actual time=16194.179..21165.116 rows=22200000 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        ->  Sort  (cost=562327.31..566715.36 rows=1755222 width=21) (actual time=16140.068..17729.869 rows=7400000 loops=3)
              Worker 1:  Sort Method: external merge  Disk: 244080kB
              Worker 0:  Sort Method: external merge  Disk: 246880kB
              Sort Method: external merge  Disk: 247800kB
"              Sort Key: api_item.id, api_record.created DESC"
              ->  Parallel Hash Join  (cost=22117.30..308287.51 rows=1755222 width=21) (actual time=5719.348..8826.655 rows=7400000 loops=3)
                    Hash Cond: (api_record_items.item_id = api_item.id)
                    ->  Parallel Hash Join  (cost=2584.61..261932.34 rows=1755222 width=21) (actual time=17.042..3748.984 rows=7400000 loops=3)
                    ->  Parallel Hash  (cost=12626.75..12626.75 rows=420875 width=8) (actual time=180.939..180.940 rows=336700 loops=3)
                          Hash Cond: (api_record_items.record_id = api_record.id)
                          Buckets: 131072  Batches: 16  Memory Usage: 3520kB
                          ->  Parallel Seq Scan on api_record_items  (cost=0.00..234956.18 rows=9291718 width=16) (actual time=0.472..1557.711 rows=7433333 loops=3)
                          ->  Parallel Seq Scan on api_item  (cost=0.00..12626.75 rows=420875 width=8) (actual time=0.022..95.567 rows=336700 loops=3)
                          ->  Parallel Hash  (cost=2402.11..2402.11 rows=14600 width=21) (actual time=16.479..16.480 rows=10012 loops=3)
                                Buckets: 32768  Batches: 1  Memory Usage: 1952kB
                                ->  Parallel Seq Scan on api_record  (cost=0.00..2402.11 rows=14600 width=21) (actual time=6.063..13.094 rows=10012 loops=3)
                                      Rows Removed by Filter: 33340
                                      Filter: ((system)::text = 'system_1'::text)

Sql相关问答推荐

PostgreSQL行级锁

带有双引号的json在Presto中是否有区别对待?

删除MariaDB数据库中的JSON数据

有没有办法在每次计算每一行的数据时更新2个值?

仅 for each 唯一ID返回一个元素,并仅返回最新连接的记录

解析SQL Server中的嵌套JSON

删除事务中的本地临时表

重新组合已排序的日期范围

基于是否具有某些数据的关联表覆盖SELECT语句中的列值

如何使用聚合连接两个表

当 ansible 变量未定义或为空时,跳过 sql.j2 模板中的 DELETE FROM 查询

如何修复初学者 SQL INNER JOIN 查询错误

替换SQL Server XML中多处出现的 node 值

统计重复记录的总数

对于小数据集,EF / SQL 语句花费的时间太长

错误:postgresql 中缺少表评级的 FROM 子句条目

为 sqlite 全文搜索 (fts) 创建触发器时出现虚拟表的不安全使用

如何在 DAX 中通过 Window 函数应用订单

Postgres:表的累积视图

多列上的 SQL UNIQUE 约束 - 它们的组合必须是唯一的还是至少其中之一?