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

PostgreSQL 15千万级传感器数据聚合查询性能优化求助

PostgreSQL 15聚合查询性能优化排查

问题背景

现有存储传感器数据的表ts_number,包含约1078万条记录(仅1073条val为NULL),表结构及索引如下:

CREATE TABLE public.ts_number (
    id int4 NOT NULL,
    ts int8 NOT NULL,
    val float4 NULL,
    CONSTRAINT ts_number_pkey PRIMARY KEY (id, ts)
);
CREATE INDEX ts_number_id_idx ON public.ts_number USING btree (id, ts, val);

需求是将所有数据按1分钟间隔聚合,原查询语句:

select
    date_trunc('minute', to_timestamp(tn.ts / 1000)) as "time",
    tn.id,
    round(avg(tn.val::numeric), 1) as value
from
    ts_number tn
where
    tn.val is not null
group by
    tn.id,
    date_trunc('minute', to_timestamp(tn.ts / 1000))
order by
    date_trunc('minute', to_timestamp(tn.ts / 1000));

原查询性能瓶颈分析

关闭JIT后的执行计划显示总耗时约46.6秒,核心瓶颈如下:

QUERY PLAN                                                                                                                              |
----------------------------------------------------------------------------------------------------------------------------------------+
Sort  (cost=1438043.51..1448107.59 rows=4025633 width=44) (actual time=45605.997..46283.850 rows=3507690 loops=1)                       |
  Sort Key: (date_trunc('minute'::text, to_timestamp(((ts / 1000))::double precision)))                                                 |
  Sort Method: external merge  Disk: 92136kB                                                                                            |
  Buffers: shared hit=32 read=79640, temp read=56259 written=100732                                                                     |
  I/O Timings: shared/local read=12067.837, temp read=1178.562 write=3915.268                                                           |
  ->  HashAggregate  (cost=707851.61..923882.68 rows=4025633 width=44) (actual time=29691.221..42565.892 rows=3507690 loops=1)          |
        Group Key: date_trunc('minute'::text, to_timestamp(((ts / 1000))::double precision)), id                                        |
        Planned Partitions: 32  Batches: 33  Memory Usage: 33041kB  Disk Usage: 368648kB                                                |
        Buffers: shared hit=32 read=79640, temp read=44742 written=89197                                                                |
        I/O Timings: shared/local read=12067.837, temp read=1147.856 write=3727.306                                                     |
        ->  Seq Scan on ts_number tn  (cost=0.00..295394.36 rows=10785399 width=16) (actual time=6.016..18958.130 rows=10785155 loops=1)|
              Filter: (val IS NOT NULL)                                                                                                 |
              Rows Removed by Filter: 1073                                                                                              |
              Buffers: shared hit=32 read=79640                                                                                         |
              I/O Timings: shared/local read=12067.837                                                                                  |
Planning Time: 0.289 ms                                                                                                                 |
Execution Time: 46617.899 ms                                                                                                            |
  1. 全表扫描IO开销:val is not null仅排除1073行,优化器选择全表扫描,读取79640个共享缓冲区耗时约12秒,是查询的基础IO成本。
  2. HashAggregate磁盘溢出:生成350多万个分组时,work_mem(10MB)不足以容纳所有分组,导致HashAggregate拆分33个批次,磁盘写入约360MB,这部分IO耗时约22秒。
  3. 排序磁盘溢出:最终对350多万行结果排序时,内存不足触发外部合并排序,额外增加IO耗时。

计算列优化后的性能分析

添加计算列ts_time并创建覆盖索引后,总耗时降至约35秒,但仍存在明显问题:

QUERY PLAN                                                                                                                                                 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------+
Finalize GroupAggregate  (cost=260210.24..262870.90 rows=10200 width=20) (actual time=28025.740..34159.907 rows=3507690 loops=1)                           |
  Group Key: ts_time, id                                                                                                                                   |
  Buffers: shared hit=86581 read=93231, temp read=31437 written=32171                                                                                      |
  I/O Timings: shared/local read=46057.413, temp read=121.378 write=1017.921                                                                               |
  ->  Gather Merge  (cost=260210.24..262590.40 rows=20400 width=44) (actual time=28025.718..31512.629 rows=3738515 loops=1)                                |
        Workers Planned: 2                                                                                                                                 |
        Workers Launched: 2                                                                                                                                |
        Buffers: shared hit=86581 read=93231, temp read=31437 written=32171                                                                                |
        I/O Timings: shared/local read=46057.413, temp read=121.378 write=1017.921                                                                         |
        ->  Sort  (cost=259210.21..259235.71 rows=10200 width=44) (actual time=23712.198..24001.505 rows=1246172 loops=3)                                  |
              Sort Key: ts_time, id                                                                                                                        |
              Sort Method: external merge  Disk: 90712kB                                                                                                   |
              Buffers: shared hit=86581 read=93231, temp read=31437 written=32171                                                                          |
              I/O Timings: shared/local read=46057.413, temp read=121.378 write=1017.921                                                                   |
              Worker 0:  Sort Method: external merge  Disk: 78008kB                                                                                        |
              Worker 1:  Sort Method: external merge  Disk: 76384kB                                                                                        |
              ->  Partial HashAggregate  (cost=258429.08..258531.08 rows=10200 width=44) (actual time=20173.138..21185.291 rows=1246172 loops=3)           |
                    Group Key: ts_time, id                                                                                                                 |
                    Batches: 5  Memory Usage: 262193kB  Disk Usage: 7536kB                                                                                 |
                    Buffers: shared hit=86553 read=93231, temp read=799 written=1530                                                                       |
                    I/O Timings: shared/local read=46057.413, temp read=3.658 write=34.272                                                                 |
                    Worker 0:  Batches: 1  Memory Usage: 237585kB                                                                                          |
                    Worker 1:  Batches: 1  Memory Usage: 237585kB                                                                                          |
                    ->  Parallel Seq Scan on ts_number tn  (cost=0.00..224726.62 rows=4493662 width=16) (actual time=2.821..16943.198 rows=3595052 loops=3)|
                          Filter: (val IS NOT NULL)                                                                                                        |
                          Rows Removed by Filter: 358                                                                                                      |
                          Buffers: shared hit=86553 read=93231                                                                                             |
                          I/O Timings: shared/local read=46057.413                                                                                         |
Planning:                                                                                                                                                  |
  Buffers: shared hit=25                                                                                                                                   |
Planning Time: 22.619 ms                                                                                                                                   |
Execution Time: 34999.933 ms                                                                                                                               |
  • 并行扫描导致磁盘IO竞争,共享缓冲区读取耗时从12秒升至46秒,成为新的主要瓶颈;
  • 排序和HashAggregate仍存在磁盘溢出,内存不足问题未解决。

进一步优化方案

1. 调高work_mem参数

当前work_mem为10MB,远不足以处理350多万个分组的聚合和排序操作。临时调整可执行:

SET work_mem = '64MB';

全局修改postgresql.conf:

work_mem = 64MB

按25个连接计算,总内存占用为25*64MB=1.6GB,加上shared_buffers的1GB,仍在分配的4GB内存范围内。调高后可避免磁盘溢出,大幅减少IO耗时。

2. 强制使用有序聚合(GroupAggregate)

创建覆盖索引并强制优化器选择索引扫描,让数据按分组键有序读取,避免HashAggregate的开销:

CREATE INDEX idx_ts_number_ts_time_id_val ON public.ts_number USING btree (ts_time, id, val) WHERE val IS NOT NULL;
-- 临时禁用全表扫描测试
SET enable_seqscan = off;

此时数据已按ts_time, id排序,可直接使用GroupAggregate,无需Hash和额外排序。

3. 优化计算列生成逻辑

原计算列使用make_interval效率较低,替换为更直接的时间转换:

ALTER TABLE public.ts_number ADD COLUMN ts_time timestamp generated always as (
    date_trunc('minute', timestamp '1970-01-01' + ts * interval '1 millisecond')
) stored;

该方式避免了make_interval的函数调用开销,计算更高效。

4. 调整并行扫描参数

当前max_parallel_workers_per_gather为2,可调整为3(服务器为4核CPU,max_parallel_workers为4),提升并行扫描效率:

max_parallel_workers_per_gather = 3

5. 预热缓存

提前将全表数据加载到缓存,后续查询可直接从内存读取:

SELECT * FROM ts_number WHERE val IS NOT NULL;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 04:55:28