我需要计算一系列测量数据的值。度量数据被划分为1000组连续行,因为查询的每个调用都必须输出一系列1000个值。典型的输入集将包含10,000至1,000,000行。以下是我能想到的最好的解决方案。它工作得很好,但是执行相当慢。因为我的要求之一是经常触发计算,所以我需要为执行时间进行优化。不幸的是,不能预先计算单个组的值,因为每个新的度量行都会影响组大小。
create table devices
(
id varchar not null
constraint devices_pkey
primary key
);
create table processes
(
id integer not null,
constraint processes_pkey
primary key (id, device_id),
device_id varchar not null
constraint fk_processes_devices
references devices
on delete cascade
);
create index processes_device_id_idx on processes (device_id);
create table measurements
(
timestamp timestamp with time zone not null,
current real not null,
process_id integer not null,
device_id varchar not null,
constraint measurements_pkey
primary key (timestamp, process_id, device_id),
constraint fk_measurements_processes
foreign key (process_id, device_id) references processes
on delete cascade
);
create index measurements_process_id_device_id_idx on measurements (device_id, process_id);
INSERT INTO devices (id) VALUES ('123');
INSERT INTO processes (id, device_id) VALUES (456, '123');
WITH numbers AS (
SELECT *
FROM generate_series(1, 1000000)
)
INSERT INTO measurements (timestamp, current, process_id, device_id)
SELECT NOW() + (generate_series * interval '1 second'), generate_series * random(), 456, '123'
FROM numbers;select min(timestamp) as timestamp,
case when sum(current) < 0 then -SQRT(AVG(POWER(current, 2))) else SQRT(AVG(POWER(current, 2))) end,
456 as process_id,
'123' as device_id,
index / 1000 as group_index
from (select timestamp,
current,
row_number() over (order by timestamp) as index
from measurements
where device_id = '123'
and process_id = 456
order by timestamp) as subquery
group by group_index
order by group_index;数据库-小提琴:https://www.db-fiddle.com/f/uVTcf9Q2JDEkPf3S5hgvfB/2
查询计划可视化:http://tatiyants.com/pev/#/plans/plan_1569689658707
如何优化查询?
Sort (cost=100157.88..100158.38 rows=200 width=60) (actual time=927.340..927.402 rows=1001 loops=1)
Sort Key: ((subquery.index / 1000))
Sort Method: quicksort Memory: 103kB
-> HashAggregate (cost=100144.74..100150.24 rows=200 width=60) (actual time=926.828..927.036 rows=1001 loops=1)
Group Key: (subquery.index / 1000)
-> Subquery Scan on subquery (cost=0.42..77644.74 rows=1000000 width=20) (actual time=0.049..704.478 rows=1000000 loops=1)
-> WindowAgg (cost=0.42..65144.74 rows=1000000 width=20) (actual time=0.046..576.692 rows=1000000 loops=1)
-> Index Scan using measurements_pkey on measurements (cost=0.42..50144.74 rows=1000000 width=12) (actual time=0.029..219.951 rows=1000000 loops=1)
Index Cond: ((process_id = 456) AND ((device_id)::text = '123'::text))
Planning Time: 0.378 ms
Execution Time: 927.591 ms发布于 2019-09-28 18:46:28
这个计划没有明显的问题,所以没有明显的大的优化要做。从根本上说,你做了很多工作,这需要很多时间。
我可能会先退一步,看看你的商业案例。你为什么需要这个输出?你能“需要”一些更容易优化的东西吗?比如,一块块完整地滑过,而不是一排一排地在遗忘的过程中慢慢地从一大块滑到另一片?或者分区是由可预测的时间戳片定义的,而不是每个分区有1000行?
你没有给出你的版本,但你的小提琴使用9.5。如果我使用最新的版本,并打开JIT (即时编译),我得到了约15%的改善比JIT关闭。
如果我构建了一个只适用于索引扫描的索引,那么我不会得到太大的改进,它有时更快,有时更慢(它还与JIT交互)。但是,如果不是所有的东西都在记忆中,它可能会带来巨大的进步:
create index on measurements (device_id, process_id, timestamp, current);如果您正在寻找更复杂的解决方案,您可以编写一个进程,该进程将连接到数据库并侦听新的插入,然后通过使用增量计算将行从一个分区滑动到下一个分区来更新数据,而不是从头开始重新计算每个分区。这类似于移动骨料的工作方式,只不过它只在语句中的框架之间工作,而不是在您需要的语句之间工作。这个应该很快。要获得严格的准确性,这将取决于每个插入的行按照它们的时间戳列的相同顺序提交--这一要求在一般情况下很难保证,但对于您的确切情况可能很容易。
https://dba.stackexchange.com/questions/249853
复制相似问题