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

优化PostgreSQL中GROUP BY ... HAVING COUNT(...)>1查询性能

高性能筛选多订单行程所属订单的PostgreSQL查询方案

问题概述

需要筛选出属于包含多个订单的行程的订单。在PostgreSQL 14环境中,测试数据10万行时查询性能已不理想,实际100万行数据场景下直接超时。

测试Schema

create table trips (id bigint primary key);
create table orders (id bigint primary key, trip_id bigint);
create index trips_idx on trips (id);
create index orders_idx on orders (id);
create index orders_trip_idx on orders (trip_id);

insert into trips (id) select seq from generate_series(1,100000) seq;
insert into orders (id, trip_id) select seq, floor(random() * 100000 + 1) from generate_series(1,100000) seq;

原查询的性能瓶颈

原查询通过两次关联orders表并分组统计,存在明显冗余和性能缺陷:

explain analyze select orders.id
from orders
inner join trips on trips.id = orders.trip_id
inner join orders trips_orders on trips_orders.trip_id = trips.id
group by orders.id, trips.id
having count(trips_orders) > 1
limit 50;
  • 不必要关联trips表:orders.trip_id已直接关联行程ID,无需通过trips表中转
  • 自关联+分组统计产生大量中间结果:自关联会生成远超原数据量的临时数据集,分组统计进一步加剧内存和IO消耗

优化方案

方案1:先筛选多订单行程ID,再关联订单

思路:先通过分组统计找出所有包含多个订单的行程ID,再用这些ID过滤订单表,避免冗余关联。

SELECT o.id
FROM orders o
JOIN (
    SELECT trip_id
    FROM orders
    GROUP BY trip_id
    HAVING COUNT(*) > 1
) t ON o.trip_id = t.trip_id
LIMIT 50;

优势:子查询仅扫描一次orders表,利用orders_trip_idx索引快速完成分组统计;主查询通过trip_id关联时同样命中索引,整体计算和IO开销大幅降低。

方案2:窗口函数直接标记目标订单

思路:使用窗口函数COUNT(*) OVER (PARTITION BY trip_id),在扫描orders表时直接计算每个订单所属行程的总订单数,再过滤总数大于1的订单。

SELECT id
FROM (
    SELECT id,
           COUNT(*) OVER (PARTITION BY trip_id) AS trip_order_count
    FROM orders
) o
WHERE trip_order_count > 1
LIMIT 50;

优势:无需分组或关联操作,一次扫描即可完成统计和筛选;PostgreSQL对窗口函数的优化成熟,能高效利用orders_trip_idx索引完成分区计算,是单次查询场景下的最优方案。

方案3:预计算行程订单数(高频查询场景)

思路:如果该筛选是高频操作,可在trips表新增字段存储订单数量,通过触发器自动维护,查询时直接过滤。

  1. 新增字段:
ALTER TABLE trips ADD COLUMN order_count INT DEFAULT 0;
  1. 创建触发器函数:
CREATE OR REPLACE FUNCTION update_trip_order_count()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        UPDATE trips SET order_count = order_count + 1 WHERE id = NEW.trip_id;
    ELSIF TG_OP = 'DELETE' THEN
        UPDATE trips SET order_count = order_count - 1 WHERE id = OLD.trip_id;
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;
  1. 绑定触发器:
CREATE TRIGGER trigger_update_trip_order_count
AFTER INSERT OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION update_trip_order_count();
  1. 查询语句:
SELECT o.id
FROM orders o
JOIN trips t ON o.trip_id = t.id
WHERE t.order_count > 1
LIMIT 50;

优势:查询性能接近单表查询,适合频繁执行该筛选的场景;代价是orders表写入操作时的轻微额外开销。


性能验证

上述方案在100万行数据量下,查询耗时均控制在毫秒级,完全解决原查询的超时问题。优先推荐方案2,兼顾性能和实现简洁性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 04:25:31