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

如何使用TimescaleDB高效获取分组版本化时序数据的最新版本

问题背景

我们正在迁移至TimescaleDB,已完成多个超4亿行、存储版本化时序预测数据的大表迁移。
其中dt_start_utc字段存储预测的实际日期,version_utc字段存储预测的发布日期(值越新代表越接近实际预测日期)。

表结构

sandbox_cord=# \d+ fc_power_raw_import_normalized
                                    Table "public.fc_power_raw_import_normalized"
        Column        |            Type             | Collation | Nullable | Default | Storage | Stats target | Description
----------------------+-----------------------------+-----------+----------+---------+---------+--------------+-------------
dt_start_utc         | timestamp without time zone |           | not null |         | plain   |              |
fc_id                | integer                     |           | not null |         | plain   |              |
fc_kwh               | integer                     |           |          |         | plain   |              |
fc_power_supplier_id | integer                     |           | not null |         | plain   |              |
fc_power_type_id     | integer                     |           | not null |         | plain   |              |
version_utc          | timestamp without time zone |           | not null |         | plain   |              |
Indexes:
    "fc_power_raw_import_normalized2_pkey" PRIMARY KEY, btree (dt_start_utc, fc_id, fc_power_supplier_id, fc_power_type_id, version_utc)
    "fc_power_raw_import_normalize2_dt_start_utc_fc_id_fc_power_s_id" btree (dt_start_utc DESC, fc_id, fc_power_supplier_id, fc_power_type_id, version_utc DESC)
    "fc_power_raw_import_normalize2_dt_start_utc_fc_power_supplie_id" btree (dt_start_utc DESC, fc_power_supplier_id, fc_power_type_id)
    "fc_power_raw_import_normalized2_dt_start_utc_idx" btree (dt_start_utc DESC)
    "fc_power_raw_import_normalized2_version_utc_idx" btree (version_utc DESC)
Triggers:
    ts_insert_blocker BEFORE INSERT ON fc_power_raw_import_normalized2 FOR EACH ROW EXECUTE FUNCTION _timescaledb_internal.insert_blocker()
Child tables: _timescaledb_internal._hyper_3_2334_chunk,
            _timescaledb_internal._hyper_3_2335_chunk,
            _timescaledb_internal._hyper_3_2336_chunk,
            _timescaledb_internal._hyper_3_2337_chunk,
            _timescaledb_internal._hyper_3_2338_chunk,
            _timescaledb_internal._hyper_3_2339_chunk
Access method: heap
...

数据示例

sandbox_cord=# SELECT * FROM fc_power_raw_import_normalized ORDER BY fc_id ASC LIMIT 25;
    dt_start_utc     | fc_id | fc_kwh | fc_power_supplier_id | fc_power_type_id |     version_utc
---------------------+-------+--------+----------------------+------------------+---------------------
2020-08-27 00:00:00 |     9 |    167 |                    5 |                1 | 2020-08-23 00:27:03
2020-08-27 00:00:00 |     9 |    150 |                    5 |                1 | 2020-08-23 01:12:37
2020-08-27 00:00:00 |     9 |    132 |                    5 |                1 | 2020-08-23 07:11:42
2020-08-27 00:00:00 |     9 |    144 |                    5 |                1 | 2020-08-23 13:12:11
2020-08-27 00:00:00 |     9 |    161 |                    5 |                1 | 2020-08-23 19:13:05
2020-08-27 00:00:00 |     9 |    166 |                    5 |                1 | 2020-08-24 01:11:53
...

当前查询实现

子查询匹配方案

SELECT *
FROM fc_power_raw_import_normalized
WHERE  (dt_start_utc, fc_id, fc_power_supplier_id, fc_power_type_id, version_utc) IN (
    SELECT
    dt_start_utc, fc_id, fc_power_supplier_id, fc_power_type_id, MAX(version_utc) version_utc
    FROM
    fc_power_raw_import_normalized
    WHERE
    dt_start_utc > now() - INTERVAL '2 weeks'
    GROUP BY
    dt_start_utc, fc_id, fc_power_supplier_id, fc_power_type_id
)
AND dt_start_utc > now() - INTERVAL '2 weeks'
ORDER by fc_id, dt_start_utc, version_utc;

TimescaleDB last() 函数方案

SELECT
dt_start_utc,
    fc_id,
    fc_power_supplier_id,
    fc_power_type_id,
    last(fc_kwh, version_utc) AS fc_kwh_last
FROM fc_power_raw_import_normalized
WHERE dt_start_utc > now () - INTERVAL '2 weeks'
GROUP BY dt_start_utc, fc_id, fc_power_supplier_id, fc_power_type_id
ORDER BY dt_start_utc ASC, fc_id ASC;

查询返回示例

dt_start_utc     | fc_id | fc_kwh | fc_power_supplier_id | fc_power_type_id |     version_utc
---------------------+-------+--------+----------------------+------------------+---------------------
2021-10-12 16:45:00 |    19 |     99 |                    4 |                1 | 2021-10-12 13:13:50
2021-10-12 16:45:00 |    19 |     99 |                    4 |                2 | 2021-10-12 13:14:47
2021-10-12 17:00:00 |    19 |    100 |                    4 |                1 | 2021-10-12 13:13:50
2021-10-12 17:00:00 |    19 |    100 |                    4 |                2 | 2021-10-12 13:14:47
2021-10-12 17:15:00 |    19 |    103 |                    4 |                1 | 2021-10-12 13:13:50
2021-10-12 17:15:00 |    19 |    103 |                    4 |                2 | 2021-10-12 13:14:47
2021-10-12 17:30:00 |    19 |    105 |                    4 |                1 | 2021-10-12 13:13:50
2021-10-12 17:30:00 |    19 |    105 |                    4 |                2 | 2021-10-12 13:14:47
2021-10-12 17:45:00 |    19 |    108 |                    4 |                1 | 2021-10-12 13:13:50
2021-10-12 17:45:00 |    19 |    108 |                    4 |                2 | 2021-10-12 13:14:47
2021-10-12 18:00:00 |    19 |    108 |                    4 |                1 | 2021-10-12 13:13:50
2021-10-12 18:00:00 |    19 |    108 |                    4 |                2 | 2021-10-12 13:14:47
2021-10-12 18:15:00 |    19 |    105 |                    4 |                1 | 2021-10-12 13:13:50
2021-10-12 18:15:00 |    19 |    105 |                    4 |                2 | 2021-10-12 13:14:47
2021-10-12 18:30:00 |    19 |     82 |                    4 |                1 | 2021-10-12 18:28:47
2021-10-12 18:30:00 |    19 |     82 |                    4 |                2 | 2021-10-12 18:29:59
2021-10-12 18:45:00 |    19 |     82 |                    4 |                1 | 2021-10-12 18:28:47
2021-10-12 18:45:00 |    19 |     82 |                    4 |                2 | 2021-10-12 18:29:59
2021-10-12 19:00:00 |    19 |     81 |                    4 |                1 | 2021-10-12 18:28:47
...

性能问题

当前查询性能很差,查询3个月数据耗时约77秒,查询时间跨度更长时耗时会更高。
已尝试过使用INNER JOIN、窗口函数改写查询,也参考相关文章添加了多个不同索引,但均未带来明显性能提升。另外还测试了continuous aggregates功能,但该功能要求定义time_bucket,不适用于当前场景,调整chunk大小也未带来明显性能提升。
目前基于该表结构和查询需求,TimescaleDB的性能和已优化的传统关系型数据库MariaDB相比没有明显优势,未达到专用时序数据库的预期性能表现。

优化建议
  • 调整复合索引顺序实现索引覆盖:现有索引均把dt_start_utc放在首位,仅适合纯按时间维度过滤的查询,不符合当前先按业务维度分组的场景。可新建复合索引(fc_id, fc_power_supplier_id, fc_power_type_id, dt_start_utc DESC, version_utc DESC) INCLUDE (fc_kwh),索引顺序匹配查询的分组、排序逻辑,同时包含查询需要的所有字段,完全避免回表扫描。
  • 用PostgreSQL原生DISTINCT ON改写查询:DISTINCT ON是PG针对「取分组第一条记录」场景做了专属优化的语法,性能远高于通用聚合、子查询匹配和TimescaleDB的last()函数,配合上面调整后的索引,查询不需要额外排序、聚合操作,直接走索引扫描即可拿到结果,改写后SQL如下:
SELECT DISTINCT ON (fc_id, fc_power_supplier_id, fc_power_type_id, dt_start_utc)
    dt_start_utc,
    fc_id,
    fc_power_supplier_id,
    fc_power_type_id,
    fc_kwh,
    version_utc
FROM fc_power_raw_import_normalized
WHERE dt_start_utc > now() - INTERVAL '2 weeks'
ORDER BY fc_id, fc_power_supplier_id, fc_power_type_id, dt_start_utc, version_utc DESC;
  • 添加跳变索引加速最值计算:针对version_utc字段创建TimescaleDB专属的跳变索引,该索引专门为时序场景下的最值查询、范围查询做了优化,可进一步降低取最新版本的计算开销。
  • 配置冷热数据分层:如果超过一定时间的历史数据不需要高频查询最新版本,可配置TimescaleDB的数据分层策略,把冷数据迁移到低成本存储层,热数据保留在高速存储介质中,进一步提升高频查询的响应速度。
  • 裁剪不必要的查询范围:如果业务允许,尽量将查询时间范围限制在合理区间,避免一次性扫描过多chunk。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 18:36:05